File indexing completed on 2024-11-10 04:57:12

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2015 Thomas Lübking <thomas.luebking@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 /*global effect, effects, animate, animationTime, Effect, QEasingCurve */
0010 
0011 "use strict";
0012 
0013 var badBadWindowsEffect = {
0014     duration: animationTime(250),
0015     showingDesktop: false,
0016     loadConfig: function () {
0017         badBadWindowsEffect.duration = animationTime(250);
0018     },
0019     setShowingDesktop: function (showing) {
0020         badBadWindowsEffect.showingDesktop = showing;
0021     },
0022     offToCorners: function (showing, frozenTime) {
0023         if (typeof frozenTime === "undefined") {
0024             frozenTime = -1;
0025         }
0026         var stackingOrder = effects.stackingOrder;
0027         var screenGeo = effects.virtualScreenGeometry;
0028         var xOffset = screenGeo.width / 16;
0029         var yOffset = screenGeo.height / 16;
0030         if (showing) {
0031             var closestWindows = [ undefined, undefined, undefined, undefined ];
0032             var movedWindowsCount = 0;
0033             for (var i = 0; i < stackingOrder.length; ++i) {
0034                 var w = stackingOrder[i];
0035 
0036                 if (!w.hiddenByShowDesktop) {
0037                     continue;
0038                 }
0039 
0040                 // ignore invisible windows and such that do not have to be restored
0041                 if (!w.visible) {
0042                     if (w.offToCornerId) {
0043                         // if it was visible when the effect was activated delete its animation data
0044                         cancel(w.offToCornerId);
0045                         delete w.offToCornerId;
0046                         delete w.apertureCorner;
0047                     }
0048                     continue;
0049                 }
0050 
0051                 // Don't touch docks
0052                 if (w.dock) {
0053                     continue;
0054                 }
0055 
0056                 // calculate the corner distances
0057                 var geo = w.geometry;
0058                 var dl = geo.x + geo.width - screenGeo.x;
0059                 var dr = screenGeo.x + screenGeo.width - geo.x;
0060                 var dt = geo.y + geo.height - screenGeo.y;
0061                 var db = screenGeo.y + screenGeo.height - geo.y;
0062                 w.apertureDistances = [ dl + dt, dr + dt, dr + db, dl + db ];
0063                 movedWindowsCount += 1;
0064 
0065                 // if this window is the closest one to any corner, set it as preferred there
0066                 var nearest = 0;
0067                 for (var j = 1; j < 4; ++j) {
0068                     if (w.apertureDistances[j] < w.apertureDistances[nearest] ||
0069                         (w.apertureDistances[j] == w.apertureDistances[nearest] && closestWindows[j] === undefined)) {
0070                         nearest = j;
0071                     }
0072                 }
0073                 if (closestWindows[nearest] === undefined ||
0074                     closestWindows[nearest].apertureDistances[nearest] > w.apertureDistances[nearest])
0075                     closestWindows[nearest] = w;
0076             }
0077 
0078             // second pass, select corners
0079 
0080             // 1st off, move the nearest windows to their nearest corners
0081             // this will ensure that if there's only on window in the lower right
0082             // it won't be moved out to the upper left
0083             var movedWindowsDec = [ 0, 0, 0, 0 ];
0084             for (var i = 0; i < 4; ++i) {
0085                 if (closestWindows[i] === undefined)
0086                     continue;
0087                 closestWindows[i].apertureCorner = i;
0088                 delete closestWindows[i].apertureDistances;
0089                 movedWindowsDec[i] = 1;
0090             }
0091 
0092             // 2nd, distribute the remainders according to their preferences
0093             // this doesn't exactly have heapsort performance ;-)
0094             movedWindowsCount = Math.floor((movedWindowsCount + 3) / 4);
0095             for (var i = 0; i < 4; ++i) {
0096                 for (var j = 0; j < movedWindowsCount - movedWindowsDec[i]; ++j) {
0097                     var bestWindow = undefined;
0098                     for (var k = 0; k < stackingOrder.length; ++k) {
0099                         if (stackingOrder[k].apertureDistances === undefined)
0100                             continue;
0101                         if (bestWindow === undefined ||
0102                             stackingOrder[k].apertureDistances[i] < bestWindow.apertureDistances[i])
0103                             bestWindow = stackingOrder[k];
0104                     }
0105                     if (bestWindow === undefined)
0106                         break;
0107                     bestWindow.apertureCorner = i;
0108                     delete bestWindow.apertureDistances;
0109                 }
0110             }
0111 
0112         }
0113 
0114         // actually re/move windows from/to assigned corners
0115         for (var i = 0; i < stackingOrder.length; ++i) {
0116             var w = stackingOrder[i];
0117             if (w.apertureCorner === undefined && w.offToCornerId === undefined)
0118                 continue;
0119 
0120             if (w.dock) {
0121                 continue;
0122             }
0123 
0124             var anchor, tx, ty;
0125             var geo = w.geometry;
0126             if (w.apertureCorner == 1 || w.apertureCorner == 2) {
0127                 tx = screenGeo.x + screenGeo.width - xOffset;
0128                 anchor = Effect.Left;
0129             } else {
0130                 tx = xOffset;
0131                 anchor = Effect.Right;
0132             }
0133             if (w.apertureCorner > 1) {
0134                 ty = screenGeo.y + screenGeo.height - yOffset;
0135                 anchor |= Effect.Top;
0136             } else {
0137                 ty = yOffset;
0138                 anchor |= Effect.Bottom;
0139             }
0140 
0141             if (showing) {
0142                 if (!w.offToCornerId || !freezeInTime(w.offToCornerId, frozenTime)) {
0143 
0144                     w.offToCornerId = set({
0145                         window: w,
0146                         duration: badBadWindowsEffect.duration,
0147                         curve: QEasingCurve.InOutCubic,
0148                         animations: [{
0149                             type: Effect.Position,
0150                             targetAnchor: anchor,
0151                             to: { value1: tx, value2: ty },
0152                             frozenTime: frozenTime
0153                         },{
0154                             type: Effect.Opacity,
0155                             to: 0.0,
0156                             frozenTime: frozenTime
0157                         }]
0158                     });
0159                 }
0160             } else {
0161                 // Reset if the window has become invisible in the meantime
0162                 if (!w.visible) {
0163                     cancel(w.offToCornerId);
0164                     delete w.offToCornerId;
0165                     delete w.apertureCorner;
0166                 // This if the window was invisible and has become visible in the meantime
0167                 } else if (!w.offToCornerId || !redirect(w.offToCornerId, Effect.Backward) || !freezeInTime(w.offToCornerId, frozenTime)) {
0168                     animate({
0169                         window: w,
0170                         duration: badBadWindowsEffect.duration,
0171                         curve: QEasingCurve.InOutCubic,
0172                         animations: [{
0173                             type: Effect.Position,
0174                             sourceAnchor: anchor,
0175                             gesture: true,
0176                             from: { value1: tx, value2: ty }
0177                         },{
0178                             type: Effect.Opacity,
0179                             from: 0.0
0180                         }]
0181                     });
0182                 }
0183             }
0184         }
0185     },
0186     animationEnded: function (w, a, meta) {
0187         // After the animation that closes the effect, reset all the parameters
0188         if (!badBadWindowsEffect.showingDesktop) {
0189             cancel(w.offToCornerId);
0190             delete w.offToCornerId;
0191             delete w.apertureCorner;
0192         }
0193     },
0194     realtimeScreenEdgeCallback: function (border, deltaProgress, effectScreen) {
0195         if (!deltaProgress || !effectScreen) {
0196             badBadWindowsEffect.offToCorners(badBadWindowsEffect.showingDesktop, -1);
0197             return;
0198         }
0199         let time = 0;
0200 
0201         switch (border) {
0202         case KWin.ElectricTop:
0203         case KWin.ElectricBottom:
0204             time = Math.min(1, (Math.abs(deltaProgress.height) / (effectScreen.geometry.height / 2))) * badBadWindowsEffect.duration;
0205             break;
0206         case KWin.ElectricLeft:
0207         case KWin.ElectricRight:
0208             time = Math.min(1, (Math.abs(deltaProgress.width) / (effectScreen.geometry.width / 2))) * badBadWindowsEffect.duration;
0209             break;
0210         default:
0211             return;
0212         }
0213         if (badBadWindowsEffect.showingDesktop) {
0214             time = badBadWindowsEffect.duration - time;
0215         }
0216 
0217         badBadWindowsEffect.offToCorners(true, time)
0218     },
0219     init: function () {
0220         badBadWindowsEffect.loadConfig();
0221         effects.showingDesktopChanged.connect(badBadWindowsEffect.setShowingDesktop);
0222         effects.showingDesktopChanged.connect(badBadWindowsEffect.offToCorners);
0223         effect.animationEnded.connect(badBadWindowsEffect.animationEnded);
0224 
0225         let edges = effect.touchEdgesForAction("show-desktop");
0226 
0227         for (let i in edges) {
0228             let edge = parseInt(edges[i]);
0229             registerRealtimeScreenEdge(edge, badBadWindowsEffect.realtimeScreenEdgeCallback);
0230         }
0231     }
0232 };
0233 
0234 badBadWindowsEffect.init();