Reduce dynamic tile drag jitter

This commit is contained in:
Marco Allegretti 2026-05-19 10:00:43 +02:00
parent c6bd37dfc3
commit 7c51f76cc0
3 changed files with 39 additions and 25 deletions

View file

@ -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)) {

View file

@ -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) {

View file

@ -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"