mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 00:47:22 +00:00
Refine Dynamic Tiling drag zones
Use edge insert zones for drag-and-drop reordering, keep drag signal connections stable across window creation, and consume task-menu tiling requests through the script settings bridge.
This commit is contained in:
parent
3f6916cafc
commit
3fbf68d56b
1 changed files with 293 additions and 70 deletions
|
|
@ -23,6 +23,7 @@ Item {
|
||||||
// ── Configuration ───────────────────────────────────────────────────────
|
// ── Configuration ───────────────────────────────────────────────────────
|
||||||
readonly property int outerGap: 8
|
readonly property int outerGap: 8
|
||||||
readonly property int innerGap: 8 // half applied to each edge → 4px per tile
|
readonly property int innerGap: 8 // half applied to each edge → 4px per tile
|
||||||
|
readonly property int insertZoneThickness: 48
|
||||||
|
|
||||||
// ── State ───────────────────────────────────────────────────────────────
|
// ── State ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -33,24 +34,31 @@ Item {
|
||||||
// Windows the user has manually floated (by UUID string).
|
// Windows the user has manually floated (by UUID string).
|
||||||
property var floatedWindows: ({})
|
property var floatedWindows: ({})
|
||||||
|
|
||||||
// Whether tiling is globally enabled.
|
// Windows whose interactive move/resize signals are already connected.
|
||||||
property bool tilingEnabled: true
|
property var dragConnectedWindows: ({})
|
||||||
|
|
||||||
|
property int lastWindowRequestSerial: ShellSettings.Settings.dynamicTilingWindowRequestSerial
|
||||||
|
|
||||||
// Drag state.
|
// Drag state.
|
||||||
//
|
//
|
||||||
// Behaviour: dragging a tile and dropping it onto ANOTHER tile swaps
|
// Behaviour: dragging a tile and dropping it onto ANOTHER tile swaps or
|
||||||
// their positions in the BSP layout. Dropping anywhere else does
|
// inserts based on the cursor zone. Dropping inside the work area but
|
||||||
// nothing (the window will be re-tiled into its original slot on the
|
// outside another tile restores the original slot. Dropping outside the
|
||||||
// next layout pass, unless KWin's native quick-tile / electric border
|
// work area floats the window, giving users a visible escape hatch from
|
||||||
// takes over — which is fine; we don't fight it).
|
// the tiled layout.
|
||||||
property var draggingWindow: null
|
property var draggingWindow: null
|
||||||
property var swapOutlineActive: false
|
property bool dragOutlineActive: false
|
||||||
|
property string dragDropMode: "" // "swap", "insert", "restore", or "float"
|
||||||
|
property rect dragOutlineRect: Qt.rect(0, 0, 0, 0)
|
||||||
|
|
||||||
// Reorder state — kept stable while dragging so the rest of the layout
|
// Reorder state — kept stable while dragging so the rest of the layout
|
||||||
// doesn't shuffle under the cursor.
|
// doesn't shuffle under the cursor.
|
||||||
property string dragSourceScreen: ""
|
property string dragSourceScreen: ""
|
||||||
property int dragSourceIndex: -1
|
property int dragSourceIndex: -1
|
||||||
|
property rect dragSourceRect: Qt.rect(0, 0, 0, 0)
|
||||||
property var dragSwapTarget: null // {screen, index, rect} of tile under cursor
|
property var dragSwapTarget: null // {screen, index, rect} of tile under cursor
|
||||||
|
property var dragInsertTarget: null // {screen, index, insertIndex, rect} preview slot
|
||||||
|
readonly property int floatEscapeMargin: 32
|
||||||
|
|
||||||
// Deferred retile queue.
|
// Deferred retile queue.
|
||||||
// The dockSpaceReserver LayerShell exclusive zone needs one Wayland
|
// The dockSpaceReserver LayerShell exclusive zone needs one Wayland
|
||||||
|
|
@ -118,12 +126,11 @@ Item {
|
||||||
if (minW > 0 && maxW > 0 && minW >= maxW) return true;
|
if (minW > 0 && maxW > 0 && minW >= maxW) return true;
|
||||||
if (minH > 0 && maxH > 0 && minH >= maxH) return true;
|
if (minH > 0 && maxH > 0 && minH >= maxH) return true;
|
||||||
// Manually floated
|
// Manually floated
|
||||||
if (floatedWindows[win.internalId]) return true;
|
if (floatedWindows[windowKey(win)]) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTileable(win) {
|
function isTileable(win) {
|
||||||
if (!tilingEnabled) return false;
|
|
||||||
if (!isConvergence()) return false;
|
if (!isConvergence()) return false;
|
||||||
if (shouldIgnore(win)) return false;
|
if (shouldIgnore(win)) return false;
|
||||||
if (shouldFloat(win)) return false;
|
if (shouldFloat(win)) return false;
|
||||||
|
|
@ -240,6 +247,8 @@ Item {
|
||||||
function addWindow(win) {
|
function addWindow(win) {
|
||||||
if (!isTileable(win)) return;
|
if (!isTileable(win)) return;
|
||||||
|
|
||||||
|
connectDragHandlers(win);
|
||||||
|
|
||||||
const output = win.output;
|
const output = win.output;
|
||||||
if (!output) return;
|
if (!output) return;
|
||||||
const name = output.name;
|
const name = output.name;
|
||||||
|
|
@ -251,7 +260,7 @@ Item {
|
||||||
// Avoid duplicates
|
// Avoid duplicates
|
||||||
const tiles = screenTiles[name];
|
const tiles = screenTiles[name];
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
for (let i = 0; i < tiles.length; i++) {
|
||||||
if (tiles[i].win.internalId === win.internalId) return;
|
if (windowKey(tiles[i].win) === windowKey(win)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles.push({ win: win, rect: Qt.rect(0, 0, 0, 0) });
|
tiles.push({ win: win, rect: Qt.rect(0, 0, 0, 0) });
|
||||||
|
|
@ -275,7 +284,7 @@ Item {
|
||||||
for (const sName in screenTiles) {
|
for (const sName in screenTiles) {
|
||||||
const tiles = screenTiles[sName];
|
const tiles = screenTiles[sName];
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
for (let i = 0; i < tiles.length; i++) {
|
||||||
if (tiles[i].win.internalId === win.internalId) {
|
if (windowKey(tiles[i].win) === windowKey(win)) {
|
||||||
tiles.splice(i, 1);
|
tiles.splice(i, 1);
|
||||||
screenTiles[sName] = tiles;
|
screenTiles[sName] = tiles;
|
||||||
retileScreen(sName);
|
retileScreen(sName);
|
||||||
|
|
@ -285,6 +294,64 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function windowKey(win) {
|
||||||
|
return win && win.internalId !== undefined ? String(win.internalId) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function findWindowById(windowId) {
|
||||||
|
const wanted = String(windowId);
|
||||||
|
const wins = KWinComponents.Workspace.windows;
|
||||||
|
for (let i = 0; i < wins.length; i++) {
|
||||||
|
if (windowKey(wins[i]) === wanted) {
|
||||||
|
return wins[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function floatWindow(win) {
|
||||||
|
const key = windowKey(win);
|
||||||
|
if (!key) return;
|
||||||
|
floatedWindows[key] = true;
|
||||||
|
removeWindow(win);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tileWindow(win) {
|
||||||
|
const key = windowKey(win);
|
||||||
|
if (!key) return;
|
||||||
|
delete floatedWindows[key];
|
||||||
|
if (isTileable(win)) {
|
||||||
|
addWindow(win);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectDragHandlers(win) {
|
||||||
|
const key = windowKey(win);
|
||||||
|
if (!key || dragConnectedWindows[key]) return;
|
||||||
|
|
||||||
|
dragConnectedWindows[key] = true;
|
||||||
|
win.interactiveMoveResizeStarted.connect(function() { root.onDragStart(win); });
|
||||||
|
win.interactiveMoveResizeStepped.connect(function(geo) { root.onDragStep(win, geo); });
|
||||||
|
win.interactiveMoveResizeFinished.connect(function() { root.onDragEnd(win); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowTilingRequest() {
|
||||||
|
const serial = ShellSettings.Settings.dynamicTilingWindowRequestSerial;
|
||||||
|
if (serial === lastWindowRequestSerial) return;
|
||||||
|
lastWindowRequestSerial = serial;
|
||||||
|
|
||||||
|
if (!isConvergence()) return;
|
||||||
|
const win = findWindowById(ShellSettings.Settings.dynamicTilingWindowRequestId);
|
||||||
|
if (!win || shouldIgnore(win)) return;
|
||||||
|
|
||||||
|
const action = ShellSettings.Settings.dynamicTilingWindowRequestAction;
|
||||||
|
if (action === "float") {
|
||||||
|
floatWindow(win);
|
||||||
|
} else if (action === "tile") {
|
||||||
|
tileWindow(win);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Keyboard navigation helpers ──────────────────────────────────────────
|
// ── Keyboard navigation helpers ──────────────────────────────────────────
|
||||||
|
|
||||||
function centreOf(rect) {
|
function centreOf(rect) {
|
||||||
|
|
@ -306,7 +373,7 @@ Item {
|
||||||
|
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
for (let i = 0; i < tiles.length; i++) {
|
||||||
const t = tiles[i];
|
const t = tiles[i];
|
||||||
if (t.win.internalId === fromWin.internalId) continue;
|
if (windowKey(t.win) === windowKey(fromWin)) continue;
|
||||||
const tc = centreOf(t.rect);
|
const tc = centreOf(t.rect);
|
||||||
const dx = tc.x - fc.x;
|
const dx = tc.x - fc.x;
|
||||||
const dy = tc.y - fc.y;
|
const dy = tc.y - fc.y;
|
||||||
|
|
@ -336,13 +403,12 @@ Item {
|
||||||
function onWindowAdded(win) {
|
function onWindowAdded(win) {
|
||||||
if (isTileable(win)) {
|
if (isTileable(win)) {
|
||||||
addWindow(win);
|
addWindow(win);
|
||||||
win.interactiveMoveResizeStarted.connect(function() { root.onDragStart(win); });
|
|
||||||
win.interactiveMoveResizeStepped.connect(function(geo) { root.onDragStep(win, geo); });
|
|
||||||
win.interactiveMoveResizeFinished.connect(function() { root.onDragEnd(win); });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWindowRemoved(win) {
|
function onWindowRemoved(win) {
|
||||||
|
const key = root.windowKey(win);
|
||||||
|
if (key) delete root.dragConnectedWindows[key];
|
||||||
root.removeWindow(win);
|
root.removeWindow(win);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -385,6 +451,10 @@ Item {
|
||||||
screenTiles = {};
|
screenTiles = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onDynamicTilingWindowRequestChanged() {
|
||||||
|
root.handleWindowTilingRequest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Drag handlers ─────────────────────────────────────────────────────
|
// ── Drag handlers ─────────────────────────────────────────────────────
|
||||||
|
|
@ -394,7 +464,7 @@ Item {
|
||||||
for (const sName in screenTiles) {
|
for (const sName in screenTiles) {
|
||||||
const tiles = screenTiles[sName];
|
const tiles = screenTiles[sName];
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
for (let i = 0; i < tiles.length; i++) {
|
||||||
if (tiles[i].win && tiles[i].win.internalId === win.internalId) {
|
if (tiles[i].win && windowKey(tiles[i].win) === windowKey(win)) {
|
||||||
return { screen: sName, index: i };
|
return { screen: sName, index: i };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -408,7 +478,7 @@ Item {
|
||||||
const tiles = screenTiles[sName];
|
const tiles = screenTiles[sName];
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
for (let i = 0; i < tiles.length; i++) {
|
||||||
const t = tiles[i];
|
const t = tiles[i];
|
||||||
if (ignoreWin && t.win && t.win.internalId === ignoreWin.internalId) continue;
|
if (ignoreWin && t.win && windowKey(t.win) === windowKey(ignoreWin)) continue;
|
||||||
const r = t.rect;
|
const r = t.rect;
|
||||||
if (!r || r.width <= 0 || r.height <= 0) continue;
|
if (!r || r.width <= 0 || r.height <= 0) continue;
|
||||||
if (cursor.x >= r.x && cursor.x <= r.x + r.width &&
|
if (cursor.x >= r.x && cursor.x <= r.x + r.width &&
|
||||||
|
|
@ -420,60 +490,230 @@ Item {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tileInsertPosition(cursor, rect) {
|
||||||
|
if (!validRect(rect)) return "";
|
||||||
|
|
||||||
|
const zone = Math.min(insertZoneThickness, Math.max(16, Math.floor(Math.min(rect.width, rect.height) / 4)));
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (nearest > zone) return "";
|
||||||
|
if (nearest === left || nearest === top) return "before";
|
||||||
|
return "after";
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertIndexForTarget(target, position) {
|
||||||
|
if (!target) return -1;
|
||||||
|
|
||||||
|
let targetIndex = target.index;
|
||||||
|
if (dragSourceScreen === target.screen && dragSourceIndex >= 0 && dragSourceIndex < targetIndex) {
|
||||||
|
targetIndex--;
|
||||||
|
}
|
||||||
|
return position === "before" ? targetIndex : targetIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function previewInsertRect(win, target, position) {
|
||||||
|
if (!target || !target.rect || dragSourceIndex < 0) return null;
|
||||||
|
|
||||||
|
const tiles = screenTiles[target.screen];
|
||||||
|
if (!tiles || !tiles[target.index]) return null;
|
||||||
|
|
||||||
|
const area = workRect(tiles[target.index].win || win);
|
||||||
|
if (!area) return null;
|
||||||
|
|
||||||
|
const count = tiles.length + (dragSourceScreen === target.screen ? 0 : 1);
|
||||||
|
const insertIndex = insertIndexForTarget(target, position);
|
||||||
|
if (insertIndex < 0 || insertIndex >= count) return null;
|
||||||
|
|
||||||
|
const rawRects = bspRects(area, count);
|
||||||
|
const gappedRects = applyGaps(rawRects, area);
|
||||||
|
return gappedRects[insertIndex] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertDraggedWindow(target) {
|
||||||
|
if (!target || dragSourceIndex < 0 || !dragSourceScreen) return;
|
||||||
|
|
||||||
|
if (dragSourceScreen === target.screen) {
|
||||||
|
const tiles = screenTiles[dragSourceScreen] ? screenTiles[dragSourceScreen].slice() : null;
|
||||||
|
if (!tiles || !tiles[dragSourceIndex]) return;
|
||||||
|
|
||||||
|
const node = tiles.splice(dragSourceIndex, 1)[0];
|
||||||
|
const insertIndex = Math.max(0, Math.min(target.insertIndex, tiles.length));
|
||||||
|
tiles.splice(insertIndex, 0, node);
|
||||||
|
screenTiles[dragSourceScreen] = tiles;
|
||||||
|
retileScreen(dragSourceScreen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceTiles = screenTiles[dragSourceScreen] ? screenTiles[dragSourceScreen].slice() : null;
|
||||||
|
const targetTiles = screenTiles[target.screen] ? screenTiles[target.screen].slice() : null;
|
||||||
|
if (!sourceTiles || !targetTiles || !sourceTiles[dragSourceIndex]) return;
|
||||||
|
|
||||||
|
const node = sourceTiles.splice(dragSourceIndex, 1)[0];
|
||||||
|
const insertIndex = Math.max(0, Math.min(target.insertIndex, targetTiles.length));
|
||||||
|
targetTiles.splice(insertIndex, 0, node);
|
||||||
|
screenTiles[dragSourceScreen] = sourceTiles;
|
||||||
|
screenTiles[target.screen] = targetTiles;
|
||||||
|
retileScreen(dragSourceScreen);
|
||||||
|
retileScreen(target.screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validRect(rect) {
|
||||||
|
return rect && rect.width > 0 && rect.height > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rectContainsPoint(rect, point) {
|
||||||
|
return validRect(rect) &&
|
||||||
|
point.x >= rect.x && point.x <= rect.x + rect.width &&
|
||||||
|
point.y >= rect.y && point.y <= rect.y + rect.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rectsClose(a, b) {
|
||||||
|
return Math.abs(a.x - b.x) < 2 &&
|
||||||
|
Math.abs(a.y - b.y) < 2 &&
|
||||||
|
Math.abs(a.width - b.width) < 2 &&
|
||||||
|
Math.abs(a.height - b.height) < 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function outsideWorkArea(win, cursor) {
|
||||||
|
const area = workRect(win);
|
||||||
|
if (!area) return false;
|
||||||
|
const expanded = Qt.rect(
|
||||||
|
area.x - floatEscapeMargin,
|
||||||
|
area.y - floatEscapeMargin,
|
||||||
|
area.width + floatEscapeMargin * 2,
|
||||||
|
area.height + floatEscapeMargin * 2
|
||||||
|
);
|
||||||
|
return !rectContainsPoint(expanded, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDragOutline(mode, rect) {
|
||||||
|
if (!validRect(rect)) {
|
||||||
|
clearDragOutline();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dragOutlineActive && dragDropMode === mode && rectsClose(dragOutlineRect, rect)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dragDropMode = mode;
|
||||||
|
dragOutlineRect = Qt.rect(rect.x, rect.y, rect.width, rect.height);
|
||||||
|
KWinComponents.Workspace.showOutline(dragOutlineRect);
|
||||||
|
dragOutlineActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearDragOutline() {
|
||||||
|
if (dragOutlineActive) {
|
||||||
|
KWinComponents.Workspace.hideOutline();
|
||||||
|
}
|
||||||
|
dragOutlineActive = false;
|
||||||
|
dragDropMode = "";
|
||||||
|
dragOutlineRect = Qt.rect(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetDragState() {
|
||||||
|
clearDragOutline();
|
||||||
|
dragSwapTarget = null;
|
||||||
|
dragInsertTarget = null;
|
||||||
|
dragSourceScreen = "";
|
||||||
|
dragSourceIndex = -1;
|
||||||
|
dragSourceRect = Qt.rect(0, 0, 0, 0);
|
||||||
|
draggingWindow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDragIntent(win, geo) {
|
||||||
|
const cursor = KWinComponents.Workspace.cursorPos;
|
||||||
|
const target = findTileAtCursor(cursor, win);
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
const position = tileInsertPosition(cursor, target.rect);
|
||||||
|
const insertRect = position !== "" ? previewInsertRect(win, target, position) : null;
|
||||||
|
if (validRect(insertRect)) {
|
||||||
|
dragSwapTarget = null;
|
||||||
|
dragInsertTarget = {
|
||||||
|
screen: target.screen,
|
||||||
|
index: target.index,
|
||||||
|
insertIndex: insertIndexForTarget(target, position),
|
||||||
|
rect: insertRect,
|
||||||
|
position: position
|
||||||
|
};
|
||||||
|
showDragOutline("insert", insertRect);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragSwapTarget = target;
|
||||||
|
dragInsertTarget = null;
|
||||||
|
showDragOutline("swap", target.rect);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragSwapTarget = null;
|
||||||
|
dragInsertTarget = null;
|
||||||
|
if (outsideWorkArea(win, cursor)) {
|
||||||
|
showDragOutline("float", validRect(geo) ? geo : win.frameGeometry);
|
||||||
|
} else {
|
||||||
|
showDragOutline("restore", dragSourceRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onDragStart(win) {
|
function onDragStart(win) {
|
||||||
if (!isConvergence()) return;
|
if (!isConvergence()) return;
|
||||||
draggingWindow = win;
|
draggingWindow = win;
|
||||||
swapOutlineActive = false;
|
clearDragOutline();
|
||||||
dragSwapTarget = null;
|
dragSwapTarget = null;
|
||||||
|
dragInsertTarget = null;
|
||||||
|
dragSourceRect = Qt.rect(0, 0, 0, 0);
|
||||||
|
|
||||||
// Remember the source slot so we can swap on drop.
|
// Remember the source slot so we can swap or insert on drop.
|
||||||
// The tile stays in screenTiles[] during the drag so the rest of
|
// The tile stays in screenTiles[] during the drag so the rest of
|
||||||
// the layout doesn't shuffle.
|
// the layout doesn't shuffle.
|
||||||
const slot = findTileSlot(win);
|
const slot = findTileSlot(win);
|
||||||
if (slot) {
|
if (slot) {
|
||||||
dragSourceScreen = slot.screen;
|
dragSourceScreen = slot.screen;
|
||||||
dragSourceIndex = slot.index;
|
dragSourceIndex = slot.index;
|
||||||
|
const tiles = screenTiles[slot.screen];
|
||||||
|
if (tiles && tiles[slot.index]) {
|
||||||
|
const rect = tiles[slot.index].rect;
|
||||||
|
dragSourceRect = Qt.rect(rect.x, rect.y, rect.width, rect.height);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dragSourceScreen = "";
|
dragSourceScreen = "";
|
||||||
dragSourceIndex = -1;
|
dragSourceIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showDragOutline("restore", dragSourceRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragStep(win, geo) {
|
function onDragStep(win, geo) {
|
||||||
if (!isConvergence()) return;
|
if (!isConvergence()) {
|
||||||
if (draggingWindow !== win) return;
|
resetDragState();
|
||||||
|
return;
|
||||||
// Only show an outline when the cursor is over another tile —
|
|
||||||
// a clear visual hint that "drop here = swap".
|
|
||||||
const cursor = KWinComponents.Workspace.cursorPos;
|
|
||||||
const target = findTileAtCursor(cursor, win);
|
|
||||||
|
|
||||||
if (target) {
|
|
||||||
if (!dragSwapTarget ||
|
|
||||||
dragSwapTarget.screen !== target.screen ||
|
|
||||||
dragSwapTarget.index !== target.index) {
|
|
||||||
dragSwapTarget = target;
|
|
||||||
KWinComponents.Workspace.showOutline(target.rect);
|
|
||||||
swapOutlineActive = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dragSwapTarget = null;
|
|
||||||
if (swapOutlineActive) {
|
|
||||||
KWinComponents.Workspace.hideOutline();
|
|
||||||
swapOutlineActive = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (draggingWindow !== win) return;
|
||||||
|
updateDragIntent(win, geo);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragEnd(win) {
|
function onDragEnd(win) {
|
||||||
if (!isConvergence()) return;
|
if (!isConvergence()) {
|
||||||
if (swapOutlineActive) {
|
resetDragState();
|
||||||
KWinComponents.Workspace.hideOutline();
|
return;
|
||||||
swapOutlineActive = false;
|
}
|
||||||
|
|
||||||
|
const finalDropMode = dragDropMode;
|
||||||
|
clearDragOutline();
|
||||||
|
|
||||||
|
if (finalDropMode === "float") {
|
||||||
|
floatWindow(win);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (finalDropMode === "insert" && dragInsertTarget) {
|
||||||
|
insertDraggedWindow(dragInsertTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dropped on another tile → swap source/target slots.
|
// Dropped on another tile → swap source/target slots.
|
||||||
if (dragSwapTarget && dragSourceScreen && dragSourceIndex >= 0) {
|
else if (dragSwapTarget && dragSourceScreen && dragSourceIndex >= 0) {
|
||||||
const sScreen = dragSourceScreen;
|
const sScreen = dragSourceScreen;
|
||||||
const sIdx = dragSourceIndex;
|
const sIdx = dragSourceIndex;
|
||||||
const tScreen = dragSwapTarget.screen;
|
const tScreen = dragSwapTarget.screen;
|
||||||
|
|
@ -506,10 +746,7 @@ Item {
|
||||||
retileScreen(dragSourceScreen);
|
retileScreen(dragSourceScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
dragSwapTarget = null;
|
resetDragState();
|
||||||
dragSourceScreen = "";
|
|
||||||
dragSourceIndex = -1;
|
|
||||||
draggingWindow = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Keyboard shortcuts ─────────────────────────────────────────────────
|
// ── Keyboard shortcuts ─────────────────────────────────────────────────
|
||||||
|
|
@ -568,13 +805,11 @@ Item {
|
||||||
onActivated: {
|
onActivated: {
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
const win = KWinComponents.Workspace.activeWindow;
|
||||||
if (!win) return;
|
if (!win) return;
|
||||||
const id = win.internalId;
|
const id = root.windowKey(win);
|
||||||
if (root.floatedWindows[id]) {
|
if (root.floatedWindows[id]) {
|
||||||
delete root.floatedWindows[id];
|
root.tileWindow(win);
|
||||||
root.addWindow(win);
|
|
||||||
} else {
|
} else {
|
||||||
root.floatedWindows[id] = true;
|
root.floatWindow(win);
|
||||||
root.removeWindow(win);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -585,21 +820,12 @@ Item {
|
||||||
text: "SHIFT Tiling: Toggle tiling on/off"
|
text: "SHIFT Tiling: Toggle tiling on/off"
|
||||||
sequence: "Meta+T"
|
sequence: "Meta+T"
|
||||||
onActivated: {
|
onActivated: {
|
||||||
root.tilingEnabled = !root.tilingEnabled;
|
ShellSettings.Settings.dynamicTilingEnabled = !ShellSettings.Settings.dynamicTilingEnabled;
|
||||||
if (root.tilingEnabled) {
|
|
||||||
const wins = KWinComponents.Workspace.windows;
|
|
||||||
for (let i = 0; i < wins.length; i++) root.addWindow(wins[i]);
|
|
||||||
} else {
|
|
||||||
root.screenTiles = {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Right-click menu ──────────────────────────────────────────────────
|
// ── Component setup ───────────────────────────────────────────────────
|
||||||
|
|
||||||
// Note: registerUserActionsMenu is a global function in KWin JS scripts.
|
|
||||||
// In declarative QML scripts it is exposed via the KWin global object.
|
|
||||||
// We wire it up after the component is complete.
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Connect to existing windows
|
// Connect to existing windows
|
||||||
const wins = KWinComponents.Workspace.windows;
|
const wins = KWinComponents.Workspace.windows;
|
||||||
|
|
@ -607,9 +833,6 @@ Item {
|
||||||
const win = wins[i];
|
const win = wins[i];
|
||||||
if (isTileable(win)) {
|
if (isTileable(win)) {
|
||||||
addWindow(win);
|
addWindow(win);
|
||||||
win.interactiveMoveResizeStarted.connect(function() { root.onDragStart(win); });
|
|
||||||
win.interactiveMoveResizeStepped.connect(function(geo) { root.onDragStep(win, geo); });
|
|
||||||
win.interactiveMoveResizeFinished.connect(function() { root.onDragEnd(win); });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue