diff --git a/application.js b/application.js index 98ca835..e1638be 100644 --- a/application.js +++ b/application.js @@ -269,16 +269,17 @@ class Application { #connectWindowGrabs() { // start snapping when the user starts moving a window this.#signals.connect(global.display, 'grab-op-begin', (display, screen, window, op) => { - if (op === Meta.GrabOp.MOVING && window.window_type === Meta.WindowType.NORMAL) { + if (op === Meta.GrabOp.MOVING && window.window_type === Meta.WindowType.NORMAL) { // reload styling this.#loadThemeColors(); const enableSnappingModifiers = mapModifierSettingToModifierType(this.#settings.settingsData.enableSnappingModifiers.value); - + const enableMultiSnappingModifiers = mapModifierSettingToModifierType(this.#settings.settingsData.enableMultiSnappingModifiers.value); + // Create WindowSnapper for each monitor const nMonitors = global.display.get_n_monitors(); for (let i = 0; i < nMonitors; i++) { const layout = this.#readOrCreateLayoutForDisplay(i, LayoutOf2x2); - const snapper = new WindowSnapper(i, layout, window, enableSnappingModifiers); + const snapper = new WindowSnapper(i, layout, window, enableSnappingModifiers, enableMultiSnappingModifiers); this.#windowSnappers.push(snapper); } } @@ -300,4 +301,4 @@ class Application { } } -module.exports = { Application, LayoutOf2x2 }; \ No newline at end of file +module.exports = { Application, LayoutOf2x2 }; diff --git a/node_tree.js b/node_tree.js index d4409f0..e95f327 100644 --- a/node_tree.js +++ b/node_tree.js @@ -294,6 +294,19 @@ class LayoutNode { return this.children.reduce((found, child) => found || child.findNode(predicate), null); } + // find all node in the tree that matches the given predicate and return them in flat list + findAllNodes(predicate) { + let result = []; + + this.forSelfAndDescendants((n) => { + if(predicate(n)) { + result.push(n); + } + }) + + return result; + } + // delete the given node in the tree if found delete(node) { let index = this.children.indexOf(node); @@ -738,10 +751,12 @@ class PreviewSplitOperation extends LayoutOperation { class SnappingOperation extends LayoutOperation { showRegions = false; #enableSnappingModifiers; + #enableMultiSnappingModifiers; - constructor(tree, enableSnappingModifiers) { + constructor(tree, enableSnappingModifiers, enableMultiSnappingModifiers) { super(tree); this.#enableSnappingModifiers = enableSnappingModifiers; + this.#enableMultiSnappingModifiers = enableMultiSnappingModifiers; } onMotion(x, y, state) { @@ -751,19 +766,28 @@ class SnappingOperation extends LayoutOperation { return this.cancel(); } + const multiSnapEnabled = this.#enableMultiSnappingModifiers.some((e) => (state & e)); + // Find node at mouse position let node = this.tree.findNodeAtPosition(x, y); if (!node) { - return this.cancel(); + if(!multiSnapEnabled){ + return this.cancel(); + } + + return OperationResult.notHandled(); } // activate the region to snap into this.showRegions = true; - this.tree.forSelfAndDescendants(n => { - n.isSnappingDestination = false; - n.isHighlighted = false; - }); + if(!multiSnapEnabled) { + this.tree.forSelfAndDescendants(n => { + n.isSnappingDestination = false; + n.isHighlighted = false; + }); + } + node.isSnappingDestination = true; node.isHighlighted = true; @@ -771,11 +795,33 @@ class SnappingOperation extends LayoutOperation { } currentSnapToRect() { - var snapToNode = this.tree.findNode(n => n.isSnappingDestination); - if (!snapToNode) { + let snapToNodes = this.tree.findAllNodes(n => n.isSnappingDestination); + + if (snapToNodes.length == 0) { return null; } - return snapToNode.snapRect(); + + return snapToNodes + .map((n) => n.snapRect()) + .reduce((rect_a, rect_b) => { + let min_x = Math.min(rect_a.x, rect_b.x); + let min_y = Math.min(rect_a.y, rect_b.y); + let max_x = Math.max( + rect_a.x + rect_a.width, + rect_b.x + rect_b.width, + ); + let max_y = Math.max( + rect_a.y + rect_a.height, + rect_b.y + rect_b.height, + ); + + return { + x: min_x, + y: min_y, + width: max_x - min_x, + height: max_y - min_y, + }; + }); } cancel() { diff --git a/settings-schema.json b/settings-schema.json index 1964a39..9074e5d 100644 --- a/settings-schema.json +++ b/settings-schema.json @@ -15,5 +15,17 @@ "SUPER": "SUPER", "SHIFT": "SHIFT" } + }, + "enableMultiSnappingModifiers": { + "type": "combobox", + "description": "Key modifier required to activate snapping to multiple arias", + "default": "", + "options": { + "(disabled)": "", + "CTRL": "CTRL", + "ALT": "ALT", + "SUPER": "SUPER", + "SHIFT": "SHIFT" + } } -} \ No newline at end of file +} diff --git a/window-snapper.js b/window-snapper.js index 84eeff9..15d8dbb 100644 --- a/window-snapper.js +++ b/window-snapper.js @@ -30,9 +30,12 @@ class WindowSnapper { // the modifier key to enable snapping #enableSnappingModifiers; + // the modifier key to enable snapping to multiple areas + #enableMultiSnappingModifiers; + #signals = new SignalManager.SignalManager(null); - constructor(displayIdx, layout, window, enableSnappingModifiers) { + constructor(displayIdx, layout, window, enableSnappingModifiers, enableMultiSnappingModifiers) { // the layout to use for the snapping operation this.#layout = layout; @@ -42,6 +45,9 @@ class WindowSnapper { // the modifier key to enable snapping this.#enableSnappingModifiers = enableSnappingModifiers; + // the modifier key to enable snapping to multiple areas + this.#enableMultiSnappingModifiers = enableMultiSnappingModifiers; + // get the size of the display let workArea = getUsableScreenArea(displayIdx); @@ -65,7 +71,7 @@ class WindowSnapper { // ensure the layout is correct for the snap area this.#layout.calculateRects(workArea.x, workArea.y, workArea.width, workArea.height); - this.#snappingOperation = new SnappingOperation(this.#layout, this.#enableSnappingModifiers); + this.#snappingOperation = new SnappingOperation(this.#layout, this.#enableSnappingModifiers, this.#enableMultiSnappingModifiers); this.#signals.connect(this.#window, 'position-changed', this.#onWindowMoved.bind(this)); } @@ -134,4 +140,4 @@ class WindowSnapper { } } -module.exports = { WindowSnapper }; \ No newline at end of file +module.exports = { WindowSnapper };