From 75b9049a8c4fc306ce9d5abf37b1192de8ed2a1a Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Wed, 20 May 2026 09:38:37 +0200 Subject: [PATCH] Page dynamic tiles by workspace Keep one layout per output and virtual desktop. Move new windows to another existing desktop when the current page is full. When a user moves a window onto a full page, replace the last focused window on that page and send it back to the source desktop. Use stable slot swaps instead of insert splits so the layout shape does not change during moves. --- .../scripts/shift-tiling/contents/ui/main.qml | 460 ++++++++++++------ tests/check-dynamic-tiles-motion.sh | 50 +- 2 files changed, 361 insertions(+), 149 deletions(-) diff --git a/kwin/scripts/shift-tiling/contents/ui/main.qml b/kwin/scripts/shift-tiling/contents/ui/main.qml index c8d8b9bf..4312dc3e 100644 --- a/kwin/scripts/shift-tiling/contents/ui/main.qml +++ b/kwin/scripts/shift-tiling/contents/ui/main.qml @@ -4,16 +4,19 @@ // SHIFT Dynamic Tiling — KWin declarative script // // Architecture: -// - One persistent layout tree per output, keyed by output.name +// - One persistent layout tree per output and virtual desktop // - Tree nodes are split groups or window leaves; leaf rects are absolute // in-screen coordinates // - Automatic add/remove rebuilds a stable master-stack tree from window // order, so normal app churn does not leave arbitrary nested splits behind -// - Explicit drag insertion still edits the tree directly because that is a -// deliberate user layout change +// - If the current page is full, new windows move to another existing +// virtual desktop with free capacity +// - The current screen can cycle between master-stack, equal columns, and +// equal rows without changing window order +// - Dragging over an existing tile swaps the two window leaves without +// changing the split tree shape // - Drag detection uses interactiveMoveResizeStarted/Stepped/Finished -// - Dragging over a tile shows the exact directional split that will be -// created on drop +// - Dragging over a tile previews the slot that will be swapped on drop // - Gap: outer 8px on screen edges, inner 8px between tiles (4px each side) import QtQuick @@ -26,18 +29,29 @@ Item { // ── Configuration ─────────────────────────────────────────────────────── readonly property int outerGap: 8 readonly property int innerGap: 8 // half applied to each edge → 4px per tile + readonly property int maxWindowsPerPage: 4 readonly property real stablePrimaryRatio: 0.58 - readonly property real insertIntentDeadZone: 0.18 + readonly property var layoutModes: ["master", "columns", "rows"] // ── State ─────────────────────────────────────────────────────────────── - // Per-screen layout tree. Key: output.name. Value: split/leaf node. + // Per-screen/per-desktop layout tree. Key: output.name + desktop key. + // Value: split/leaf node. property var screenLayouts: ({}) - // Per-screen tile list derived from screenLayouts. Key: output.name. + // Per-screen/per-desktop tile list derived from screenLayouts. // Value: [{win, rect}], where rect is absolute in-screen coordinates. property var screenTiles: ({}) + // Per-screen/per-desktop layout mode. Key: output.name + desktop key. + // Value: one of layoutModes. + property var screenLayoutModes: ({}) + + // Last focused tiled window per screen/desktop page. Used as the + // replacement target when a user manually moves another window onto a + // full page. + property var lastFocusedWindowKeys: ({}) + // Windows the user has manually floated (by UUID string). property var floatedWindows: ({}) @@ -48,14 +62,14 @@ Item { // Drag state. // - // Behaviour: dragging a tile over another tile previews the exact - // left/right/up/down split that will be created on drop. Dropping inside + // Behaviour: dragging a tile over another tile previews the target slot + // that will be swapped on drop. Dropping inside // the work area but outside another tile restores the original slot. // Dropping outside the work area floats the window, giving users a visible // escape hatch from the tiled layout. property var draggingWindow: null property bool dragOutlineActive: false - property string dragDropMode: "" // "insert", "restore", or "float" + property string dragDropMode: "" // "swap", "restore", or "float" property rect dragOutlineRect: Qt.rect(0, 0, 0, 0) // Reorder state — kept stable while dragging so the rest of the layout @@ -63,7 +77,7 @@ Item { property string dragSourceScreen: "" property int dragSourceIndex: -1 property rect dragSourceRect: Qt.rect(0, 0, 0, 0) - property var dragInsertTarget: null // {screen, key, direction, rect} preview slot + property var dragSwapTarget: null // {screen, key, rect} preview slot readonly property int floatEscapeMargin: 32 // Deferred retile queue. @@ -97,6 +111,101 @@ Item { retileTimer.restart(); } + function desktopKey(desktop) { + if (!desktop) return ""; + if (desktop.x11DesktopNumber !== undefined) return String(desktop.x11DesktopNumber); + + const desktops = KWinComponents.Workspace.desktops; + const index = desktops ? desktops.indexOf(desktop) : -1; + return index >= 0 ? String(index + 1) : String(desktop); + } + + function desktopForWindow(win) { + if (win && win.desktops && win.desktops.length > 0) { + return win.desktops[0]; + } + return KWinComponents.Workspace.currentDesktop; + } + + function layoutKeyFor(outputName, desktop) { + const key = desktopKey(desktop); + return outputName && key !== "" ? outputName + "::" + key : ""; + } + + function desktopForLayoutKey(outputName) { + const separator = outputName.lastIndexOf("::"); + if (separator < 0) return null; + + const wantedKey = outputName.slice(separator + 2); + const desktops = KWinComponents.Workspace.desktops; + if (!desktops) return null; + + for (let i = 0; i < desktops.length; i++) { + if (desktopKey(desktops[i]) === wantedKey) { + return desktops[i]; + } + } + return null; + } + + function layoutKeyForWindow(win) { + if (!win || !win.output) return ""; + return layoutKeyFor(win.output.name, desktopForWindow(win)); + } + + function assignWindowToDesktop(win, desktop) { + if (!win || !desktop) return; + if (win.desktops && win.desktops.length === 1 && win.desktops[0] === desktop) return; + win.desktops = [desktop]; + } + + function windowCountForLayout(outputName) { + const leaves = []; + collectLeaves(screenLayouts[outputName], leaves); + let count = 0; + for (let i = 0; i < leaves.length; i++) { + const win = leaves[i].win; + if (win && !win.deleted && isTileable(win) && layoutKeyForWindow(win) === outputName) { + count++; + } + } + return count; + } + + function targetLayoutForNewWindow(win) { + const currentDesktop = desktopForWindow(win); + const currentKey = layoutKeyForWindow(win); + if (!win || !win.output || currentKey === "") { + return { key: "", desktop: null }; + } + if (windowCountForLayout(currentKey) < maxWindowsPerPage) { + return { key: currentKey, desktop: currentDesktop }; + } + + const desktops = KWinComponents.Workspace.desktops; + if (!desktops || desktops.length < 2) { + return { key: currentKey, desktop: currentDesktop }; + } + + let startIndex = desktops.indexOf(currentDesktop); + if (startIndex < 0) { + startIndex = desktops.indexOf(KWinComponents.Workspace.currentDesktop); + } + if (startIndex < 0) { + startIndex = 0; + } + + for (let offset = 1; offset < desktops.length; offset++) { + const desktop = desktops[(startIndex + offset) % desktops.length]; + const key = layoutKeyFor(win.output.name, desktop); + if (key !== "" && windowCountForLayout(key) < maxWindowsPerPage) { + return { key: key, desktop: desktop }; + } + } + + return { key: currentKey, desktop: currentDesktop }; + } + // ── Tiling guard ──────────────────────────────────────────────────────── // Active when convergence is on, gaming is off, AND the user has @@ -147,7 +256,7 @@ Item { function workRect(win) { const output = win.output; - const desktop = win.desktops[0]; + const desktop = desktopForWindow(win); if (!output || !desktop) return null; return KWinComponents.Workspace.clientArea( KWinComponents.Workspace.MaximizeArea, output, desktop); @@ -218,16 +327,6 @@ Item { }; } - function splitOrientation(rect) { - return rect && rect.width >= rect.height ? "vertical" : "horizontal"; - } - - function directionOrientation(direction, rect) { - if (direction === "left" || direction === "right") return "vertical"; - if (direction === "up" || direction === "down") return "horizontal"; - return splitOrientation(rect); - } - function splitRect(rect, orientation, ratio) { const splitRatio = ratio || 0.5; if (orientation === "vertical") { @@ -255,10 +354,49 @@ Item { collectLeaves(node.second, leaves); } - function lastLeafKey(node) { - if (!node) return ""; - if (node.kind === "leaf") return windowKey(node.win); - return lastLeafKey(node.second) || lastLeafKey(node.first); + function leafIndexForWindow(outputName, win) { + const key = windowKey(win); + if (!key) return -1; + + const leaves = []; + collectLeaves(screenLayouts[outputName], leaves); + for (let i = 0; i < leaves.length; i++) { + if (windowKey(leaves[i].win) === key) { + return i; + } + } + return -1; + } + + function leafForSlot(outputName, preferredIndex) { + const leaves = []; + collectLeaves(screenLayouts[outputName], leaves); + if (leaves.length === 0) return null; + if (preferredIndex >= 0 && preferredIndex < leaves.length) { + return leaves[preferredIndex]; + } + return leaves[leaves.length - 1]; + } + + function rememberFocusedWindow(win) { + if (!isTileable(win)) return; + + const outputName = layoutKeyForWindow(win); + const key = windowKey(win); + if (!outputName || !key || !containsLeaf(screenLayouts[outputName], key)) return; + + const focused = Object.assign({}, lastFocusedWindowKeys); + focused[outputName] = key; + lastFocusedWindowKeys = focused; + } + + function replacementLeafForLayout(outputName, fallbackIndex) { + const focusedKey = lastFocusedWindowKeys[outputName] || ""; + const focusedLeaf = focusedKey ? findLeaf(screenLayouts[outputName], focusedKey) : null; + if (focusedLeaf && focusedLeaf.win && layoutKeyForWindow(focusedLeaf.win) === outputName) { + return focusedLeaf; + } + return leafForSlot(outputName, fallbackIndex); } function orderedWindowsForScreen(outputName) { @@ -271,7 +409,7 @@ Item { const win = leaves[i].win; const key = windowKey(win); if (!key || seen[key] || !isTileable(win)) continue; - if (!win.output || win.output.name !== outputName) continue; + if (layoutKeyForWindow(win) !== outputName) continue; seen[key] = true; windows.push(win); } @@ -306,9 +444,40 @@ Item { ); } - function buildStableLayout(windows) { + function buildLinearLayout(windows, startIndex, orientation) { + const remaining = windows.length - startIndex; + if (remaining <= 0) return null; + if (remaining === 1) return makeLeaf(windows[startIndex]); + + return makeSplit( + orientation, + makeLeaf(windows[startIndex]), + buildLinearLayout(windows, startIndex + 1, orientation), + 1 / remaining + ); + } + + function layoutModeForScreen(outputName) { + const mode = screenLayoutModes[outputName]; + return layoutModes.indexOf(mode) >= 0 ? mode : "master"; + } + + function setLayoutModeForScreen(outputName, mode) { + if (!outputName || layoutModes.indexOf(mode) < 0) return; + const modes = Object.assign({}, screenLayoutModes); + modes[outputName] = mode; + screenLayoutModes = modes; + } + + function buildStableLayout(windows, mode) { if (!windows || windows.length === 0) return null; if (windows.length === 1) return makeLeaf(windows[0]); + if (mode === "columns") { + return buildLinearLayout(windows, 0, "vertical"); + } + if (mode === "rows") { + return buildLinearLayout(windows, 0, "horizontal"); + } if (windows.length === 2) { return makeSplit("vertical", makeLeaf(windows[0]), makeLeaf(windows[1]), 0.5); } @@ -322,7 +491,33 @@ Item { } function setStableLayout(outputName, windows) { - setScreenLayout(outputName, buildStableLayout(windows)); + setScreenLayout(outputName, buildStableLayout(windows, layoutModeForScreen(outputName))); + } + + function outputNameForActiveWindow() { + const win = KWinComponents.Workspace.activeWindow; + const activeName = layoutKeyForWindow(win); + if (activeName !== "") return activeName; + for (const name in screenLayouts) { + return name; + } + return ""; + } + + function cycleLayoutMode(outputName) { + if (!outputName) return; + + const currentMode = layoutModeForScreen(outputName); + const currentIndex = layoutModes.indexOf(currentMode); + const nextMode = layoutModes[(currentIndex + 1) % layoutModes.length]; + setLayoutModeForScreen(outputName, nextMode); + + const windows = orderedWindowsForScreen(outputName); + if (windows.length > 0) { + setStableLayout(outputName, windows); + retileScreen(outputName); + scheduleRetile(outputName); + } } function containsLeaf(node, key) { @@ -331,52 +526,6 @@ Item { return containsLeaf(node.first, key) || containsLeaf(node.second, key); } - function splitLeaf(node, targetKey, newLeaf, direction, fallbackRect) { - if (!node || !targetKey || !newLeaf) return node; - - if (node.kind === "leaf") { - if (windowKey(node.win) !== targetKey) return node; - - const baseRect = validRect(node.rect) ? node.rect : fallbackRect; - const orientation = directionOrientation(direction, baseRect); - if (direction === "left" || direction === "up") { - return makeSplit(orientation, newLeaf, node, 0.5); - } - return makeSplit(orientation, node, newLeaf, 0.5); - } - - node.first = splitLeaf(node.first, targetKey, newLeaf, direction, fallbackRect); - node.second = splitLeaf(node.second, targetKey, newLeaf, direction, fallbackRect); - return node; - } - - function removeLeaf(node, key) { - if (!node || !key) return { node: node, leaf: null }; - - if (node.kind === "leaf") { - if (windowKey(node.win) === key) { - return { node: null, leaf: node }; - } - return { node: node, leaf: null }; - } - - let first = removeLeaf(node.first, key); - if (first.leaf) { - node.first = first.node; - if (!node.first) return { node: node.second, leaf: first.leaf }; - return { node: node, leaf: first.leaf }; - } - - let second = removeLeaf(node.second, key); - if (second.leaf) { - node.second = second.node; - if (!node.second) return { node: node.first, leaf: second.leaf }; - return { node: node, leaf: second.leaf }; - } - - return { node: node, leaf: null }; - } - function layoutTree(node, area, leaves) { if (!node || !area) return; node.rect = Qt.rect(area.x, area.y, area.width, area.height); @@ -417,7 +566,7 @@ Item { } if (!leafKey || seen[leafKey] || !isTileable(leafWindow)) continue; - if (!leafWindow.output || leafWindow.output.name !== outputName) continue; + if (layoutKeyForWindow(leafWindow) !== outputName) continue; seen[leafKey] = true; remaining.push(leafWindow); } @@ -427,29 +576,28 @@ Item { return true; } - function moveWindowToSplit(target) { - if (!target || !draggingWindow || !dragSourceScreen) return; + function swapWindowBetweenLayouts(win, sourceName, targetName) { + if (!win || !sourceName || !targetName || sourceName === targetName) return false; - const sourceKey = windowKey(draggingWindow); - if (!sourceKey || sourceKey === target.key) return; + const sourceDesktop = desktopForLayoutKey(sourceName); + const sourceLeaf = findLeaf(screenLayouts[sourceName], windowKey(win)); + if (!sourceDesktop || !sourceLeaf) return false; - const sourceRoot = screenLayouts[dragSourceScreen]; - const detached = removeLeaf(sourceRoot, sourceKey); - if (!detached.leaf) return; + const sourceIndex = leafIndexForWindow(sourceName, win); + const targetLeaf = replacementLeafForLayout(targetName, sourceIndex); + if (!targetLeaf || !targetLeaf.win || windowKey(targetLeaf.win) === windowKey(win)) return false; - setScreenLayout(dragSourceScreen, detached.node); + const displacedWindow = targetLeaf.win; + targetLeaf.win = win; + sourceLeaf.win = displacedWindow; + assignWindowToDesktop(displacedWindow, sourceDesktop); - const targetRoot = screenLayouts[target.screen]; - if (!targetRoot || !containsLeaf(targetRoot, target.key)) { - setScreenLayout(dragSourceScreen, sourceRoot); - return; - } - - setScreenLayout(target.screen, splitLeaf(targetRoot, target.key, detached.leaf, target.direction, target.rect)); - retileScreen(dragSourceScreen); - if (target.screen !== dragSourceScreen) { - retileScreen(target.screen); - } + retileScreen(sourceName); + retileScreen(targetName); + scheduleRetile(sourceName); + scheduleRetile(targetName); + KWinComponents.Workspace.activeWindow = win; + return true; } // Recompute and apply layout for a single screen. @@ -528,9 +676,13 @@ Item { if (!output) return; const currentName = screenNameForWindow(win); - const targetName = output.name; + const targetName = layoutKeyForWindow(win); + if (targetName === "") return; if (currentName !== "" && currentName !== targetName) { + if (windowCountForLayout(targetName) >= maxWindowsPerPage && swapWindowBetweenLayouts(win, currentName, targetName)) { + return; + } removeWindowFromLayout(currentName, win); scheduleRetile(currentName); } @@ -553,7 +705,15 @@ Item { const output = win.output; if (!output) return; - const name = output.name; + const initialName = layoutKeyForWindow(win); + const target = targetLayoutForNewWindow(win); + if (target.key === "") return; + const name = target.key; + + if (target.desktop && initialName !== name) { + assignWindowToDesktop(win, target.desktop); + KWinComponents.Workspace.currentDesktop = target.desktop; + } // Avoid duplicates const key = windowKey(win); @@ -628,6 +788,9 @@ Item { win.interactiveMoveResizeStarted.connect(function() { root.onDragStart(win); }); win.interactiveMoveResizeStepped.connect(function(geo) { root.onDragStep(win, geo); }); win.interactiveMoveResizeFinished.connect(function() { root.onDragEnd(win); }); + if (win.desktopsChanged !== undefined) { + win.desktopsChanged.connect(function() { root.reconcileWindowOutput(win); }); + } } function handleWindowTilingRequest() { @@ -650,7 +813,7 @@ Item { function promoteWindow(win) { if (!isTileable(win)) return; - const outputName = screenNameForWindow(win) || (win.output ? win.output.name : ""); + const outputName = screenNameForWindow(win) || layoutKeyForWindow(win); if (!outputName) return; const key = windowKey(win); @@ -684,7 +847,7 @@ Item { // Find the tile on-screen whose centre is most in `direction` from `fromRect`. // direction: "left"|"right"|"up"|"down" function findNeighbour(fromWin, direction) { - const outputName = fromWin.output ? fromWin.output.name : null; + const outputName = layoutKeyForWindow(fromWin); if (!outputName) return null; const tiles = screenTiles[outputName]; if (!tiles) return null; @@ -735,6 +898,10 @@ Item { root.removeWindow(win); } + function onActiveWindowChanged() { + root.rememberFocusedWindow(KWinComponents.Workspace.activeWindow); + } + function onScreensChanged() { if (!root.isConvergence()) { return; @@ -763,6 +930,8 @@ Item { // Clear all tiles — the convergentwindows script will re-maximize screenLayouts = {}; screenTiles = {}; + screenLayoutModes = {}; + lastFocusedWindowKeys = {}; } } @@ -770,6 +939,8 @@ Item { if (ShellSettings.Settings.gamingModeEnabled) { screenLayouts = {}; screenTiles = {}; + screenLayoutModes = {}; + lastFocusedWindowKeys = {}; } else if (isConvergence()) { const wins = KWinComponents.Workspace.windows; for (let i = 0; i < wins.length; i++) { @@ -788,6 +959,8 @@ Item { // Tiling turned off — leave windows where they are. screenLayouts = {}; screenTiles = {}; + screenLayoutModes = {}; + lastFocusedWindowKeys = {}; } } @@ -813,7 +986,9 @@ Item { // Find the tile under a cursor position, ignoring the dragged window. function findTileAtCursor(cursor, ignoreWin) { + const activeName = dragSourceScreen || layoutKeyForWindow(ignoreWin); for (const sName in screenTiles) { + if (activeName && sName !== activeName) continue; const tiles = screenTiles[sName]; for (let i = 0; i < tiles.length; i++) { const t = tiles[i]; @@ -829,34 +1004,31 @@ Item { return null; } - function tileInsertDirection(cursor, rect) { - if (!validRect(rect)) return ""; - - const relativeX = (cursor.x - rect.x) / rect.width; - const relativeY = (cursor.y - rect.y) / rect.height; - const fromCenterX = relativeX - 0.5; - const fromCenterY = relativeY - 0.5; - if (Math.abs(fromCenterX) < insertIntentDeadZone && Math.abs(fromCenterY) < insertIntentDeadZone) { - return ""; + function findLeaf(node, key) { + if (!node || !key) return null; + if (node.kind === "leaf") { + return windowKey(node.win) === key ? node : null; } - - if (Math.abs(fromCenterX) >= Math.abs(fromCenterY)) { - return fromCenterX < 0 ? "left" : "right"; - } - return fromCenterY < 0 ? "up" : "down"; + return findLeaf(node.first, key) || findLeaf(node.second, key); } - function previewInsertRect(win, target, position) { - if (!target || !validRect(target.rect) || dragSourceIndex < 0) return null; + function swapDraggedWindow(target) { + if (!target || !draggingWindow || !dragSourceScreen) return; - const orientation = directionOrientation(position, target.rect); - const rects = splitRect(target.rect, orientation, 0.5); - if (position === "left" || position === "up") return rects[0]; - return rects[1]; - } + const sourceKey = windowKey(draggingWindow); + if (!sourceKey || sourceKey === target.key || target.screen !== dragSourceScreen) return; - function insertDraggedWindow(target) { - moveWindowToSplit(target); + const rootNode = screenLayouts[dragSourceScreen]; + const sourceLeaf = findLeaf(rootNode, sourceKey); + const targetLeaf = findLeaf(rootNode, target.key); + if (!sourceLeaf || !targetLeaf) return; + + const sourceWindow = sourceLeaf.win; + sourceLeaf.win = targetLeaf.win; + targetLeaf.win = sourceWindow; + retileScreen(dragSourceScreen); + scheduleRetile(dragSourceScreen); + KWinComponents.Workspace.activeWindow = sourceWindow; } function validRect(rect) { @@ -909,7 +1081,7 @@ Item { function resetDragState() { clearDragOutline(); - dragInsertTarget = null; + dragSwapTarget = null; dragSourceScreen = ""; dragSourceIndex = -1; dragSourceRect = Qt.rect(0, 0, 0, 0); @@ -920,23 +1092,17 @@ Item { const cursor = KWinComponents.Workspace.cursorPos; const target = findTileAtCursor(cursor, win); - if (target) { - const position = tileInsertDirection(cursor, target.rect); - const insertRect = previewInsertRect(win, target, position); - if (validRect(insertRect)) { - dragInsertTarget = { - screen: target.screen, - key: target.key, - direction: position, - rect: insertRect, - position: position - }; - showDragOutline("insert", insertRect); - return; - } + if (target && target.screen === dragSourceScreen && validRect(target.rect)) { + dragSwapTarget = { + screen: target.screen, + key: target.key, + rect: target.rect + }; + showDragOutline("swap", target.rect); + return; } - dragInsertTarget = null; + dragSwapTarget = null; if (outsideWorkArea(win, cursor)) { showDragOutline("float", validRect(geo) ? geo : win.frameGeometry); } else { @@ -948,10 +1114,10 @@ Item { if (!isConvergence()) return; draggingWindow = win; clearDragOutline(); - dragInsertTarget = null; + dragSwapTarget = null; dragSourceRect = Qt.rect(0, 0, 0, 0); - // Remember the source slot so we can insert on drop. + // Remember the source slot so we can swap on drop. // The tile stays in screenTiles[] during the drag so the rest of // the layout doesn't shuffle. const slot = findTileSlot(win); @@ -993,8 +1159,8 @@ Item { floatWindow(win); } - else if (finalDropMode === "insert" && dragInsertTarget) { - insertDraggedWindow(dragInsertTarget); + else if (finalDropMode === "swap" && dragSwapTarget) { + swapDraggedWindow(dragSwapTarget); } // Dropped elsewhere → restore the source tile to its original slot. @@ -1082,6 +1248,13 @@ Item { } } + KWinComponents.ShortcutHandler { + name: "SHIFT Tiling Cycle Layout" + text: "SHIFT Tiling: Cycle layout mode" + sequence: "Meta+Shift+T" + onActivated: root.cycleLayoutMode(root.outputNameForActiveWindow()) + } + // Tiling on/off KWinComponents.ShortcutHandler { name: "SHIFT Tiling Toggle" @@ -1103,5 +1276,6 @@ Item { addWindow(win); } } + rememberFocusedWindow(KWinComponents.Workspace.activeWindow); } } diff --git a/tests/check-dynamic-tiles-motion.sh b/tests/check-dynamic-tiles-motion.sh index eeaa86e1..821c269c 100644 --- a/tests/check-dynamic-tiles-motion.sh +++ b/tests/check-dynamic-tiles-motion.sh @@ -42,6 +42,7 @@ require_line "$env_config" '"shift-tile-preview"' require_line "$env_config" '"shift-tiling"' require_line "$effect_qml" "KWinComponents.SceneEffect" +require_line "$effect_qml" "visible: false" require_line "$effect_qml" "ShellSettings.Settings.dynamicTilingEnabled" require_line "$effect_qml" "interactiveMoveResizeStarted.connect" require_line "$effect_qml" "interactiveMoveResizeStepped.connect" @@ -50,31 +51,68 @@ require_line "$effect_qml" "Behavior on x" require_line "$effect_qml" "Behavior on y" require_line "$effect_qml" "Behavior on width" require_line "$effect_qml" "Behavior on height" -require_line "$effect_qml" "showPreview(\"insert\"" +require_line "$effect_qml" "showPreview(\"swap\"" require_line "$effect_qml" "showPreview(\"float\"" require_line "$effect_qml" "showPreview(\"restore\"" -require_line "$effect_qml" "readonly property real insertIntentDeadZone: 0.18" -require_line "$effect_qml" "if (Math.abs(fromCenterX) < insertIntentDeadZone && Math.abs(fromCenterY) < insertIntentDeadZone)" +require_line "$tiling_script" "readonly property int maxWindowsPerPage: 4" require_line "$tiling_script" "readonly property real stablePrimaryRatio: 0.58" -require_line "$tiling_script" "readonly property real insertIntentDeadZone: 0.18" +require_line "$tiling_script" "readonly property var layoutModes: [\"master\", \"columns\", \"rows\"]" +require_line "$tiling_script" "function desktopKey(desktop)" +require_line "$tiling_script" "function desktopForWindow(win)" +require_line "$tiling_script" "function layoutKeyFor(outputName, desktop)" +require_line "$tiling_script" "function desktopForLayoutKey(outputName)" +require_line "$tiling_script" "function layoutKeyForWindow(win)" +require_line "$tiling_script" "function assignWindowToDesktop(win, desktop)" +require_line "$tiling_script" "function targetLayoutForNewWindow(win)" +require_line "$tiling_script" "function leafIndexForWindow(outputName, win)" +require_line "$tiling_script" "function leafForSlot(outputName, preferredIndex)" +require_line "$tiling_script" "property var lastFocusedWindowKeys: ({})" +require_line "$tiling_script" "function rememberFocusedWindow(win)" +require_line "$tiling_script" "function replacementLeafForLayout(outputName, fallbackIndex)" +require_line "$tiling_script" "function swapWindowBetweenLayouts(win, sourceName, targetName)" +require_line "$tiling_script" "windowCountForLayout(currentKey) < maxWindowsPerPage" +require_line "$tiling_script" "windowCountForLayout(targetName) >= maxWindowsPerPage && swapWindowBetweenLayouts(win, currentName, targetName)" +require_line "$tiling_script" "KWinComponents.Workspace.currentDesktop = target.desktop" +require_line "$tiling_script" "assignWindowToDesktop(displacedWindow, sourceDesktop)" +require_line "$tiling_script" "const targetLeaf = replacementLeafForLayout(targetName, sourceIndex)" +require_line "$tiling_script" "function onActiveWindowChanged()" +require_line "$tiling_script" "root.rememberFocusedWindow(KWinComponents.Workspace.activeWindow)" +require_line "$tiling_script" "win.desktops = [desktop]" +require_line "$tiling_script" "win.desktopsChanged.connect" require_line "$tiling_script" "function orderedWindowsForScreen(outputName)" require_line "$tiling_script" "function buildStableStack(windows, startIndex)" -require_line "$tiling_script" "function buildStableLayout(windows)" +require_line "$tiling_script" "function buildLinearLayout(windows, startIndex, orientation)" +require_line "$tiling_script" "function layoutModeForScreen(outputName)" +require_line "$tiling_script" "function buildStableLayout(windows, mode)" require_line "$tiling_script" "function setStableLayout(outputName, windows)" +require_line "$tiling_script" "function cycleLayoutMode(outputName)" +require_line "$tiling_script" "function swapDraggedWindow(target)" +require_line "$tiling_script" "showDragOutline(\"swap\", target.rect)" +require_line "$tiling_script" "swapDraggedWindow(dragSwapTarget)" require_line "$tiling_script" "setStableLayout(outputName, windows)" require_line "$tiling_script" "setStableLayout(outputName, remaining)" require_line "$tiling_script" "retileScreen(name)" require_line "$tiling_script" "retileScreen(targetName)" -require_line "$tiling_script" "if (Math.abs(fromCenterX) < insertIntentDeadZone && Math.abs(fromCenterY) < insertIntentDeadZone)" require_line "$tiling_script" "function promoteWindow(win)" require_line "$tiling_script" "setStableLayout(outputName, reordered)" require_line "$tiling_script" "name: \"SHIFT Tiling Promote Primary\"" require_line "$tiling_script" "sequence: \"Meta+Shift+Return\"" +require_line "$tiling_script" "name: \"SHIFT Tiling Cycle Layout\"" +require_line "$tiling_script" "sequence: \"Meta+Shift+T\"" reject_line "$tiling_script" "targetKey = lastLeafKey(rootNode)" +reject_line "$tiling_script" "function lastLeafKey(node)" +reject_line "$tiling_script" "win.output.name !== outputName" +reject_line "$tiling_script" "const targetName = output.name" +reject_line "$tiling_script" "const desktop = win.desktops[0]" reject_line "$tiling_script" "setScreenLayout(outputName, splitLeaf(rootNode, targetKey" +reject_line "$tiling_script" "function moveWindowToSplit(target)" +reject_line "$tiling_script" "function splitLeaf(node, targetKey" +reject_line "$tiling_script" "function tileInsertDirection(cursor, rect)" +reject_line "$tiling_script" "showDragOutline(\"insert\"" reject_line "$tiling_script" "KWinComponents.Workspace.showOutline(dragOutlineRect)" reject_line "$tiling_script" "KWinComponents.Workspace.hideOutline()" +reject_line "$effect_qml" "effect.visible = true" printf '%s\n' 'dynamic-tiles-motion-ok' \ No newline at end of file