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