%PDF- %PDF-
| Direktori : /usr/share/gnome-shell/extensions/tiling-assistant@ubuntu.com/src/extension/ |
| Current File : //usr/share/gnome-shell/extensions/tiling-assistant@ubuntu.com/src/extension/resizeHandler.js |
import { Clutter, Meta } from '../dependencies/gi.js';
import { Orientation, Settings } from '../common.js';
import { Rect, Util } from './utility.js';
import { TilingWindowManager as Twm } from './tilingWindowManager.js';
const Side = {
NONE: 0,
SAME_H: 1,
OPPOSING_H: 2,
SAME_V: 4,
OPPOSING_V: 8
};
/**
* This class gets to handle the resize events of windows (whether they are
* tiled or not). If a window isn't tiled, nothing happens. If the resized
* window is tiled, auto-resize the complementing tiled windows. Intercardinal
* resizing is split into its [H]orizontal and [V]ertical components.
*/
export default class TilingResizeHandler {
constructor() {
const isResizing = grabOp => {
switch (grabOp) {
case Meta.GrabOp.RESIZING_N:
case Meta.GrabOp.RESIZING_NW:
case Meta.GrabOp.RESIZING_NE:
case Meta.GrabOp.RESIZING_S:
case Meta.GrabOp.RESIZING_SW:
case Meta.GrabOp.RESIZING_SE:
case Meta.GrabOp.RESIZING_E:
case Meta.GrabOp.RESIZING_W:
return true;
default:
return false;
}
};
const g1 = global.display.connect('grab-op-begin', (d, window, grabOp) => {
grabOp &= ~1024; // META_GRAB_OP_WINDOW_FLAG_UNCONSTRAINED
if (window && isResizing(grabOp))
this._onResizeStarted(window, grabOp);
});
const g2 = global.display.connect('grab-op-end', (d, window, grabOp) => {
grabOp &= ~1024; // META_GRAB_OP_WINDOW_FLAG_UNCONSTRAINED
if (window && isResizing(grabOp))
this._onResizeFinished(window, grabOp);
});
this._displaySignals = [];
this._displaySignals.push(g1);
this._displaySignals.push(g2);
this._sizeChangedId = 0;
this._preGrabRects = new Map();
// Save the windows, which are to be resized (passively) along the
// actively grabbed one, and a resizeOp. A resizeOp saves the side
// of the window, which will be passively resized, relative to the
// actively resized window.
this._resizeOps = new Map();
}
destroy() {
this._displaySignals.forEach(sId => global.display.disconnect(sId));
}
_onResizeStarted(window, grabOp) {
if (!window.isTiled)
return;
// Use the same margin for the alignment and equality check below.
const margin = 5;
let topTileGroup = Twm.getTopTileGroup();
topTileGroup.forEach(w => {
this._preGrabRects.set(w, new Rect(w.get_frame_rect()));
if (w !== window)
// There is no snapping for tiled windows, if the user set a window
// gap. So the windows may not align properly, if the user tried
// to manually resize them to be edge to edge. In that case, assume
// that windows that are within a certain margin distance to each
// other are meant to align and resize them together.
w.tiledRect.tryAlignWith(window.tiledRect, margin);
});
// Windows can be part of multiple tile groups. We however only resize
// the top most visible tile group. That means a tile group in a lower
// stack position may share windows with the top tile group and after
// the resize op those windows will no longer match with the lower tile
// group's tiles. So remove the shared windows from the lower tile group.
const allWindows = Twm.getWindows();
allWindows.forEach(w => {
if (!w.isTiled)
return;
if (topTileGroup.includes(w))
return;
// Gets a tile group of windows without the ones
// which are about to be resized
const group = Twm.getTileGroupFor(w);
const newGroup = group.reduce((gr, win) => {
!topTileGroup.includes(win) && gr.push(win);
return gr;
}, []);
// Tile groups are the same
if (group.length === newGroup.length)
return;
// Remove old tile group and create new one
Twm.clearTilingProps(w.get_id());
Twm.updateTileGroup(newGroup);
});
// Remove the actively resizing window to get the windows, which will
// be passively resized.
topTileGroup.splice(topTileGroup.indexOf(window), 1);
const grabbedRect = window.tiledRect;
// Holding Ctrl allows resizing windows which only directly (or transitively)
// border the window being actively resized (instead of the entire tileGroup)
const isCtrlPressed = Util.isModPressed(Clutter.ModifierType.CONTROL_MASK);
const singleEdgeResizeOp = [
Meta.GrabOp.RESIZING_N,
Meta.GrabOp.RESIZING_S,
Meta.GrabOp.RESIZING_W,
Meta.GrabOp.RESIZING_E
];
if (isCtrlPressed && singleEdgeResizeOp.includes(grabOp))
topTileGroup = this._getWindowsForIndividualResize(window, topTileGroup, grabOp);
switch (grabOp) {
// Resizing cardinal directions
case Meta.GrabOp.RESIZING_N:
for (const otherWindow of topTileGroup) {
const otherRect = otherWindow.tiledRect;
const resizeOp = ResizeOp.createResizeOp(
Util.equal(grabbedRect.y, otherRect.y, margin),
Util.equal(grabbedRect.y, otherRect.y2, margin),
false,
false
);
resizeOp && this._resizeOps.set(otherWindow, resizeOp);
}
this._sizeChangedId = window.connect('size-changed',
this._onResizing.bind(this, window, grabOp, null));
break;
case Meta.GrabOp.RESIZING_S:
for (const otherWindow of topTileGroup) {
const otherRect = otherWindow.tiledRect;
const resizeOp = ResizeOp.createResizeOp(
Util.equal(grabbedRect.y2, otherRect.y2, margin),
Util.equal(grabbedRect.y2, otherRect.y, margin),
false,
false
);
resizeOp && this._resizeOps.set(otherWindow, resizeOp);
}
this._sizeChangedId = window.connect('size-changed',
this._onResizing.bind(this, window, grabOp, null));
break;
case Meta.GrabOp.RESIZING_E:
for (const otherWindow of topTileGroup) {
const otherRect = otherWindow.tiledRect;
const resizeOp = ResizeOp.createResizeOp(
false,
false,
Util.equal(grabbedRect.x2, otherRect.x2, margin),
Util.equal(grabbedRect.x2, otherRect.x, margin)
);
resizeOp && this._resizeOps.set(otherWindow, resizeOp);
}
this._sizeChangedId = window.connect('size-changed',
this._onResizing.bind(this, window, null, grabOp));
break;
case Meta.GrabOp.RESIZING_W:
for (const otherWindow of topTileGroup) {
const otherRect = otherWindow.tiledRect;
const resizeOp = ResizeOp.createResizeOp(
false,
false,
Util.equal(grabbedRect.x, otherRect.x, margin),
Util.equal(grabbedRect.x, otherRect.x2, margin)
);
resizeOp && this._resizeOps.set(otherWindow, resizeOp);
}
this._sizeChangedId = window.connect('size-changed',
this._onResizing.bind(this, window, null, grabOp));
break;
// Resizing intercardinal directions:
case Meta.GrabOp.RESIZING_NW:
for (const otherWindow of topTileGroup) {
const otherRect = otherWindow.tiledRect;
const resizeOp = ResizeOp.createResizeOp(
Util.equal(grabbedRect.y, otherRect.y, margin),
Util.equal(grabbedRect.y, otherRect.y2, margin),
Util.equal(grabbedRect.x, otherRect.x, margin),
Util.equal(grabbedRect.x, otherRect.x2, margin)
);
resizeOp && this._resizeOps.set(otherWindow, resizeOp);
}
this._sizeChangedId = window.connect('size-changed',
this._onResizing.bind(this, window, Meta.GrabOp.RESIZING_N, Meta.GrabOp.RESIZING_W));
break;
case Meta.GrabOp.RESIZING_NE:
for (const otherWindow of topTileGroup) {
const otherRect = otherWindow.tiledRect;
const resizeOp = ResizeOp.createResizeOp(
Util.equal(grabbedRect.y, otherRect.y, margin),
Util.equal(grabbedRect.y, otherRect.y2, margin),
Util.equal(grabbedRect.x2, otherRect.x2, margin),
Util.equal(grabbedRect.x2, otherRect.x, margin)
);
resizeOp && this._resizeOps.set(otherWindow, resizeOp);
}
this._sizeChangedId = window.connect('size-changed',
this._onResizing.bind(this, window, Meta.GrabOp.RESIZING_N, Meta.GrabOp.RESIZING_E));
break;
case Meta.GrabOp.RESIZING_SW:
for (const otherWindow of topTileGroup) {
const otherRect = otherWindow.tiledRect;
const resizeOp = ResizeOp.createResizeOp(
Util.equal(grabbedRect.y2, otherRect.y2, margin),
Util.equal(grabbedRect.y2, otherRect.y, margin),
Util.equal(grabbedRect.x, otherRect.x, margin),
Util.equal(grabbedRect.x, otherRect.x2, margin)
);
resizeOp && this._resizeOps.set(otherWindow, resizeOp);
}
this._sizeChangedId = window.connect('size-changed',
this._onResizing.bind(this, window, Meta.GrabOp.RESIZING_S, Meta.GrabOp.RESIZING_W));
break;
case Meta.GrabOp.RESIZING_SE:
for (const otherWindow of topTileGroup) {
const otherRect = otherWindow.tiledRect;
const resizeOp = ResizeOp.createResizeOp(
Util.equal(grabbedRect.y2, otherRect.y2, margin),
Util.equal(grabbedRect.y2, otherRect.y, margin),
Util.equal(grabbedRect.x2, otherRect.x2, margin),
Util.equal(grabbedRect.x2, otherRect.x, margin)
);
resizeOp && this._resizeOps.set(otherWindow, resizeOp);
}
this._sizeChangedId = window.connect('size-changed',
this._onResizing.bind(this, window, Meta.GrabOp.RESIZING_S, Meta.GrabOp.RESIZING_E));
}
}
// Update the windows' tiledRects
_onResizeFinished(window, grabOp) {
if (this._sizeChangedId) {
window.disconnect(this._sizeChangedId);
this._sizeChangedId = 0;
}
if (!window.isTiled)
return;
const monitor = window.get_monitor();
const screenTopGap = Util.useIndividualGaps(monitor)
? Util.getScaledGap(Settings.SCREEN_TOP_GAP, monitor)
: Util.getScaledGap(Settings.SINGLE_SCREEN_GAP, monitor);
const screenLeftGap = Util.useIndividualGaps(monitor)
? Util.getScaledGap(Settings.SCREEN_LEFT_GAP, monitor)
: Util.getScaledGap(Settings.SINGLE_SCREEN_GAP, monitor);
const windowGap = Util.getScaledGap(Settings.WINDOW_GAP, monitor);
const workArea = window.get_work_area_for_monitor(monitor);
// First calculate the new tiledRect for window:
// The new x / y coord for the window's tiledRect can be calculated by
// a simple difference because resizing on the E / S side won't change
// x / y and resizing on the N or W side will translate into a 1:1 shift
const grabbedsNewRect = new Rect(window.get_frame_rect());
const grabbedsOldRect = this._preGrabRects.get(window);
const isResizingW = (grabOp & Meta.GrabOp.RESIZING_W) > 1;
// Shift the tiledRect by the resize amount
let newGrabbedTiledRectX = window.tiledRect.x + (grabbedsNewRect.x - grabbedsOldRect.x);
// Switch the screenGap for a windowGap
if (isResizingW && window.tiledRect.x === workArea.x)
newGrabbedTiledRectX = newGrabbedTiledRectX + screenLeftGap - windowGap / 2;
// Same as W but different orientation
const isResizingN = (grabOp & Meta.GrabOp.RESIZING_N) > 1;
let newGrabbedTiledRectY = window.tiledRect.y + (grabbedsNewRect.y - grabbedsOldRect.y);
if (isResizingN && window.tiledRect.y === workArea.y)
newGrabbedTiledRectY = newGrabbedTiledRectY + screenTopGap - windowGap / 2;
// If resizing on the E side, you can simply rely on get_frame_rect's
// new width else x2 should stick to where it was (manual calc due
// special cases like gnome-terminal)
const isResizingE = (grabOp & Meta.GrabOp.RESIZING_E) > 1;
const newGrabbedTiledRectWidth = isResizingE
? grabbedsNewRect.width + windowGap / 2 + (workArea.x === newGrabbedTiledRectX ? screenLeftGap : windowGap / 2)
: window.tiledRect.x2 - newGrabbedTiledRectX;
// Same principal applies to the height and resizing on the S side
const isResizingS = (grabOp & Meta.GrabOp.RESIZING_S) > 1;
const newGrabbedTiledRectHeight = isResizingS
? grabbedsNewRect.height + windowGap / 2 + (workArea.y === newGrabbedTiledRectY ? screenTopGap : windowGap / 2)
: window.tiledRect.y2 - newGrabbedTiledRectY;
const grabbedsOldTiledRect = window.tiledRect;
window.tiledRect = new Rect(
newGrabbedTiledRectX,
newGrabbedTiledRectY,
newGrabbedTiledRectWidth,
newGrabbedTiledRectHeight
);
// Now calculate the new tiledRects for the windows, which were resized
// along the window based on the diff of the window's tiledRect pre
// and after the grab.
const tiledRectDiffX = window.tiledRect.x - grabbedsOldTiledRect.x;
const tiledRectDiffY = window.tiledRect.y - grabbedsOldTiledRect.y;
const tiledRectDiffWidth = window.tiledRect.width - grabbedsOldTiledRect.width;
const tiledRectDiffHeight = window.tiledRect.height - grabbedsOldTiledRect.height;
this._resizeOps.forEach((resizeOp, win) => {
if (win === window)
return;
if (resizeOp.side & Side.SAME_H) {
win.tiledRect.x += tiledRectDiffX;
win.tiledRect.width += tiledRectDiffWidth;
} else if (resizeOp.side & Side.OPPOSING_H) {
win.tiledRect.x += isResizingE ? tiledRectDiffWidth : 0;
win.tiledRect.width -= tiledRectDiffWidth;
}
if (resizeOp.side & Side.SAME_V) {
win.tiledRect.y += tiledRectDiffY;
win.tiledRect.height += tiledRectDiffHeight;
} else if (resizeOp.side & Side.OPPOSING_V) {
win.tiledRect.y += isResizingS ? tiledRectDiffHeight : 0;
win.tiledRect.height -= tiledRectDiffHeight;
}
});
this._preGrabRects.clear();
this._resizeOps.clear();
}
_onResizing(resizedWindow, grabOpV, grabOpH) {
this._resizeOps.forEach((resizeOp, window) => {
const rectV = this._getPassiveResizedRect(grabOpV, resizedWindow, window,
resizeOp.side & Side.SAME_V, resizeOp.side & Side.OPPOSING_V);
const rectH = this._getPassiveResizedRect(grabOpH, resizedWindow, window,
resizeOp.side & Side.SAME_H, resizeOp.side & Side.OPPOSING_H);
if (rectV && rectH)
window.move_resize_frame(false, rectH[0], rectV[1], rectH[2], rectV[3]);
else if (rectV)
window.move_resize_frame(false, ...rectV);
else if (rectH)
window.move_resize_frame(false, ...rectH);
});
}
// Gets the rect for the non-grabbed window adapted to the resized
// grabbed window *but* only adapted for 1 side (either vertically
// or horizontally) at a time based on grabOp
_getPassiveResizedRect(grabOp, resizedWindow, window,
resizeOnSameSide, resizeOnOpposingSide) {
if (!grabOp)
return null;
if (!resizeOnSameSide && !resizeOnOpposingSide)
return null;
const resizedRect = new Rect(resizedWindow.get_frame_rect());
const wRect = new Rect(window.get_frame_rect());
const preGrabRect = this._preGrabRects.get(window);
const windowGap = Util.getScaledGap(Settings.WINDOW_GAP, window.get_monitor());
switch (grabOp) {
case Meta.GrabOp.RESIZING_N:
return resizeOnSameSide
? [wRect.x, resizedRect.y, wRect.width, preGrabRect.y2 - resizedRect.y]
: [wRect.x, wRect.y, wRect.width, resizedRect.y - wRect.y - windowGap];
case Meta.GrabOp.RESIZING_S:
return resizeOnSameSide
? [wRect.x, wRect.y, wRect.width, resizedRect.y2 - preGrabRect.y]
: [wRect.x, resizedRect.y2 + windowGap, wRect.width, preGrabRect.y2 - resizedRect.y2 - windowGap];
case Meta.GrabOp.RESIZING_W:
return resizeOnSameSide
? [resizedRect.x, wRect.y, preGrabRect.x2 - resizedRect.x, wRect.height]
: [wRect.x, wRect.y, resizedRect.x - wRect.x - windowGap, wRect.height];
case Meta.GrabOp.RESIZING_E:
return resizeOnSameSide
? [wRect.x, wRect.y, resizedRect.x2 - preGrabRect.x, wRect.height]
: [resizedRect.x2 + windowGap, wRect.y, preGrabRect.x2 - resizedRect.x2 - windowGap, wRect.height];
}
}
/**
* Gets the windows which should be resized for the 'individual' resize mode.
* That means all windows that directly (or transitively) border the window
* being resized at the resized edge.
*
* @param {Meta.Window} window the window that is actively resized.
* @param {Meta.Window[]} tileGroup the top tile group.
* @param {Meta.GrabOp} grabOp
* @returns {Meta.Window[]} all windows that will resize using the individual resize mode
*/
_getWindowsForIndividualResize(window, tileGroup, grabOp) {
// Resizes on the same side as the one being resized by the user.
// Starts with the window that is actively being resized by the user.
const sameSide = [window];
// Resizes on the opposite side as the one being resized by the user
const oppositeSide = [];
const resizeIsNOrW = [Meta.GrabOp.RESIZING_N, Meta.GrabOp.RESIZING_W].includes(grabOp);
const orientation = [Meta.GrabOp.RESIZING_N, Meta.GrabOp.RESIZING_S].includes(grabOp)
? Orientation.V : Orientation.H;
// Checks if the w1 and w2 border each other at a certain edge.
const borders = (w1, w2, w1IsAfterW2) => {
const [start, end] = orientation === Orientation.H ? ['x', 'x2'] : ['y', 'y2'];
const overlap = orientation === Orientation.H ? 'vertOverlap' : 'horizOverlap';
if (w1IsAfterW2) {
return w1.tiledRect[start] === w2.tiledRect[end] &&
w1.tiledRect[overlap](w2.tiledRect);
} else {
return w1.tiledRect[end] === w2.tiledRect[start] &&
w1.tiledRect[overlap](w2.tiledRect);
}
};
/**
* @param {Meta.Window[]} uncheckedWindows windows yet to be checked.
* @param {Meta.Window[]} checkingWindows windows, which the bordering windows
* are searched for. It initially only starts with the window that is
* actively resized by the user.
* @param {Meta.Window[]} borderingWindows the windows that border the
* checkingWindows on the resized edge.
* @param {boolean} sideDeterminant determines which edge the
* bordering is checked on. It's the relation of the checkingWindows
* to the actively resized windows.
*/
const findBorderingWindows = (uncheckedWindows, checkingWindows,
borderingWindows, sideDeterminant) => {
const oldCount = borderingWindows.length;
checkingWindows.forEach(w => {
uncheckedWindows.forEach(unchecked => {
if (borders(w, unchecked, sideDeterminant))
borderingWindows.push(unchecked);
});
});
if (oldCount !== borderingWindows.length) {
// Find the bordering windows for the newly added windows by
// flipping the checkingWindows and borderingWindows arrays as well as
// the side that is checked with the borders function.
findBorderingWindows(
uncheckedWindows.filter(w => !borderingWindows.includes(w)),
borderingWindows,
checkingWindows,
!sideDeterminant
);
}
};
findBorderingWindows(tileGroup, sameSide, oppositeSide, resizeIsNOrW);
return [...sameSide, ...oppositeSide];
}
}
/**
* Saves information on which side a window will resize to complement the
* grabbed window. A non-grabbed window can resize on the 'same side', on
* the 'opposing side' or not at all. For ex.: Resizing the top-left quarter
* on the E side means the bottom-left quarter resizes on the same side (E)
* and the top / bottom-right quarters resize on the opposing side (W). If
* the bottom window wasn't quartered but instead had its width equal the
* workArea.width, then it wouldn't resize at all.
*/
const ResizeOp = class ResizeOp {
/**
* @param {number} side
*/
constructor(side) {
this.side = side;
}
/**
* @param {boolean} resizeOnSameSideV
* @param {boolean} resizeOnOpposingSideV
* @param {boolean} resizeOnSameSideH
* @param {boolean} resizeOnOpposingSideH
* @returns {ResizeOp|null}
*/
static createResizeOp(resizeOnSameSideV, resizeOnOpposingSideV,
resizeOnSameSideH, resizeOnOpposingSideH) {
let verticalResizeSide = Side.NONE;
let horizontalResizeSide = Side.NONE;
if (resizeOnSameSideV)
verticalResizeSide = Side.SAME_V;
else if (resizeOnOpposingSideV)
verticalResizeSide = Side.OPPOSING_V;
if (resizeOnSameSideH)
horizontalResizeSide = Side.SAME_H;
else if (resizeOnOpposingSideH)
horizontalResizeSide = Side.OPPOSING_H;
const resizeSide = verticalResizeSide | horizontalResizeSide;
return resizeSide ? new ResizeOp(resizeSide) : null;
}
};