From 7c51f76cc0fda37de6dc44aeaa9bf7cb5acd8f2f Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Tue, 19 May 2026 10:00:43 +0200 Subject: [PATCH] Reduce dynamic tile drag jitter --- .../shift-tile-preview/contents/ui/main.qml | 35 ++++++++++--------- .../scripts/shift-tiling/contents/ui/main.qml | 23 +++++++----- tests/check-dynamic-tiles-motion.sh | 6 ++++ 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/kwin/effects/shift-tile-preview/contents/ui/main.qml b/kwin/effects/shift-tile-preview/contents/ui/main.qml index 85832d21..5942924d 100644 --- a/kwin/effects/shift-tile-preview/contents/ui/main.qml +++ b/kwin/effects/shift-tile-preview/contents/ui/main.qml @@ -16,6 +16,7 @@ KWinComponents.SceneEffect { readonly property int floatEscapeMargin: 32 readonly property int previewAnimationDuration: 180 readonly property int previewFadeDuration: 110 + readonly property real insertIntentDeadZone: 0.18 property var dragConnectedWindows: ({}) property var draggingWindow: null @@ -101,26 +102,25 @@ KWinComponents.SceneEffect { } function insertDirection(cursor, geometry) { - const leftDistance = Math.abs(cursor.x - geometry.x); - const rightDistance = Math.abs((geometry.x + geometry.width) - cursor.x); - const topDistance = Math.abs(cursor.y - geometry.y); - const bottomDistance = Math.abs((geometry.y + geometry.height) - cursor.y); - const nearestDistance = Math.min(leftDistance, rightDistance, topDistance, bottomDistance); + const relativeX = (cursor.x - geometry.x) / geometry.width; + const relativeY = (cursor.y - geometry.y) / geometry.height; + const fromCenterX = relativeX - 0.5; + const fromCenterY = relativeY - 0.5; + if (Math.abs(fromCenterX) < insertIntentDeadZone && Math.abs(fromCenterY) < insertIntentDeadZone) { + return ""; + } - if (nearestDistance === leftDistance) { - return "left"; + if (Math.abs(fromCenterX) >= Math.abs(fromCenterY)) { + return fromCenterX < 0 ? "left" : "right"; } - if (nearestDistance === rightDistance) { - return "right"; - } - if (nearestDistance === topDistance) { - return "up"; - } - return "down"; + return fromCenterY < 0 ? "up" : "down"; } function previewRectFor(cursor, targetGeometry) { const direction = insertDirection(cursor, targetGeometry); + if (direction === "") { + return null; + } const orientation = direction === "left" || direction === "right" ? "vertical" : "horizontal"; const splitGeometries = splitRect(targetGeometry, orientation); if (direction === "left" || direction === "up") { @@ -208,8 +208,11 @@ KWinComponents.SceneEffect { const cursor = KWinComponents.Workspace.cursorPos; const targetGeometry = findTileAtCursor(cursor, window); if (targetGeometry) { - showPreview("insert", previewRectFor(cursor, targetGeometry), window.output.name); - return; + const insertGeometry = previewRectFor(cursor, targetGeometry); + if (validRect(insertGeometry)) { + showPreview("insert", insertGeometry, window.output.name); + return; + } } if (outsideWorkArea(window, cursor)) { diff --git a/kwin/scripts/shift-tiling/contents/ui/main.qml b/kwin/scripts/shift-tiling/contents/ui/main.qml index 58610d37..54e0c30b 100644 --- a/kwin/scripts/shift-tiling/contents/ui/main.qml +++ b/kwin/scripts/shift-tiling/contents/ui/main.qml @@ -27,6 +27,7 @@ Item { readonly property int outerGap: 8 readonly property int innerGap: 8 // half applied to each edge → 4px per tile readonly property real stablePrimaryRatio: 0.58 + readonly property real insertIntentDeadZone: 0.18 // ── State ─────────────────────────────────────────────────────────────── @@ -540,6 +541,7 @@ Item { if (win.maximizable) win.setMaximize(false, false); win.noBorder = false; + retileScreen(targetName); scheduleRetile(targetName); } @@ -569,6 +571,7 @@ Item { // (Same pattern as convergentwindows constrainAfterRestoreTimer.) if (win.maximizable) win.setMaximize(false, false); win.noBorder = false; + retileScreen(name); scheduleRetile(name); } @@ -801,16 +804,18 @@ Item { function tileInsertDirection(cursor, rect) { if (!validRect(rect)) return ""; - const left = Math.abs(cursor.x - rect.x); - const right = Math.abs((rect.x + rect.width) - cursor.x); - const top = Math.abs(cursor.y - rect.y); - const bottom = Math.abs((rect.y + rect.height) - cursor.y); - const nearest = Math.min(left, right, top, bottom); + 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 ""; + } - if (nearest === left) return "left"; - if (nearest === right) return "right"; - if (nearest === top) return "up"; - return "down"; + if (Math.abs(fromCenterX) >= Math.abs(fromCenterY)) { + return fromCenterX < 0 ? "left" : "right"; + } + return fromCenterY < 0 ? "up" : "down"; } function previewInsertRect(win, target, position) { diff --git a/tests/check-dynamic-tiles-motion.sh b/tests/check-dynamic-tiles-motion.sh index 39eb31b1..a5f0a9db 100644 --- a/tests/check-dynamic-tiles-motion.sh +++ b/tests/check-dynamic-tiles-motion.sh @@ -53,14 +53,20 @@ require_line "$effect_qml" "Behavior on height" require_line "$effect_qml" "showPreview(\"insert\"" 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 real stablePrimaryRatio: 0.58" +require_line "$tiling_script" "readonly property real insertIntentDeadZone: 0.18" 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 setStableLayout(outputName, windows)" 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)" reject_line "$tiling_script" "targetKey = lastLeafKey(rootNode)" reject_line "$tiling_script" "setScreenLayout(outputName, splitLeaf(rootNode, targetKey"