mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 16:57:43 +00:00
Stabilize automatic tile layouts
This commit is contained in:
parent
e0e51d7ffd
commit
c6bd37dfc3
2 changed files with 104 additions and 24 deletions
|
|
@ -7,9 +7,10 @@
|
||||||
// - One persistent layout tree per output, keyed by output.name
|
// - One persistent layout tree per output, keyed by output.name
|
||||||
// - Tree nodes are split groups or window leaves; leaf rects are absolute
|
// - Tree nodes are split groups or window leaves; leaf rects are absolute
|
||||||
// in-screen coordinates
|
// in-screen coordinates
|
||||||
// - On add/remove/reorder the tree is updated and then laid out for the
|
// - Automatic add/remove rebuilds a stable master-stack tree from window
|
||||||
// affected screen, preserving the split structure instead of rebuilding a
|
// order, so normal app churn does not leave arbitrary nested splits behind
|
||||||
// fresh linear BSP from window count
|
// - Explicit drag insertion still edits the tree directly because that is a
|
||||||
|
// deliberate user layout change
|
||||||
// - Drag detection uses interactiveMoveResizeStarted/Stepped/Finished
|
// - Drag detection uses interactiveMoveResizeStarted/Stepped/Finished
|
||||||
// - Dragging over a tile shows the exact directional split that will be
|
// - Dragging over a tile shows the exact directional split that will be
|
||||||
// created on drop
|
// created on drop
|
||||||
|
|
@ -25,6 +26,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 real stablePrimaryRatio: 0.58
|
||||||
|
|
||||||
// ── State ───────────────────────────────────────────────────────────────
|
// ── State ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -258,6 +260,70 @@ Item {
|
||||||
return lastLeafKey(node.second) || lastLeafKey(node.first);
|
return lastLeafKey(node.second) || lastLeafKey(node.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function orderedWindowsForScreen(outputName) {
|
||||||
|
const windows = [];
|
||||||
|
const seen = {};
|
||||||
|
const leaves = [];
|
||||||
|
collectLeaves(screenLayouts[outputName], leaves);
|
||||||
|
|
||||||
|
for (let i = 0; i < leaves.length; i++) {
|
||||||
|
const win = leaves[i].win;
|
||||||
|
const key = windowKey(win);
|
||||||
|
if (!key || seen[key] || !isTileable(win)) continue;
|
||||||
|
if (!win.output || win.output.name !== outputName) continue;
|
||||||
|
seen[key] = true;
|
||||||
|
windows.push(win);
|
||||||
|
}
|
||||||
|
|
||||||
|
return windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendWindowIfMissing(windows, win) {
|
||||||
|
const key = windowKey(win);
|
||||||
|
if (!key) return false;
|
||||||
|
|
||||||
|
for (let i = 0; i < windows.length; i++) {
|
||||||
|
if (windowKey(windows[i]) === key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
windows.push(win);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStableStack(windows, startIndex) {
|
||||||
|
const remaining = windows.length - startIndex;
|
||||||
|
if (remaining <= 0) return null;
|
||||||
|
if (remaining === 1) return makeLeaf(windows[startIndex]);
|
||||||
|
|
||||||
|
return makeSplit(
|
||||||
|
"horizontal",
|
||||||
|
makeLeaf(windows[startIndex]),
|
||||||
|
buildStableStack(windows, startIndex + 1),
|
||||||
|
1 / remaining
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStableLayout(windows) {
|
||||||
|
if (!windows || windows.length === 0) return null;
|
||||||
|
if (windows.length === 1) return makeLeaf(windows[0]);
|
||||||
|
if (windows.length === 2) {
|
||||||
|
return makeSplit("vertical", makeLeaf(windows[0]), makeLeaf(windows[1]), 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeSplit(
|
||||||
|
"vertical",
|
||||||
|
makeLeaf(windows[0]),
|
||||||
|
buildStableStack(windows, 1),
|
||||||
|
stablePrimaryRatio
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStableLayout(outputName, windows) {
|
||||||
|
setScreenLayout(outputName, buildStableLayout(windows));
|
||||||
|
}
|
||||||
|
|
||||||
function containsLeaf(node, key) {
|
function containsLeaf(node, key) {
|
||||||
if (!node || !key) return false;
|
if (!node || !key) return false;
|
||||||
if (node.kind === "leaf") return windowKey(node.win) === key;
|
if (node.kind === "leaf") return windowKey(node.win) === key;
|
||||||
|
|
@ -325,24 +391,9 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertWindowInLayout(outputName, win) {
|
function insertWindowInLayout(outputName, win) {
|
||||||
const rootNode = screenLayouts[outputName];
|
const windows = orderedWindowsForScreen(outputName);
|
||||||
const newLeaf = makeLeaf(win);
|
if (!appendWindowIfMissing(windows, win)) return;
|
||||||
|
setStableLayout(outputName, windows);
|
||||||
if (!rootNode) {
|
|
||||||
setScreenLayout(outputName, newLeaf);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetKey = "";
|
|
||||||
const active = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (active && active.output && active.output.name === outputName && windowKey(active) !== windowKey(win)) {
|
|
||||||
targetKey = windowKey(active);
|
|
||||||
}
|
|
||||||
if (!containsLeaf(rootNode, targetKey)) {
|
|
||||||
targetKey = lastLeafKey(rootNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
setScreenLayout(outputName, splitLeaf(rootNode, targetKey, newLeaf, "", workRect(win)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeWindowFromLayout(outputName, win) {
|
function removeWindowFromLayout(outputName, win) {
|
||||||
|
|
@ -350,9 +401,28 @@ Item {
|
||||||
const rootNode = screenLayouts[outputName];
|
const rootNode = screenLayouts[outputName];
|
||||||
if (!key || !rootNode) return false;
|
if (!key || !rootNode) return false;
|
||||||
|
|
||||||
const result = removeLeaf(rootNode, key);
|
const leaves = [];
|
||||||
if (!result.leaf) return false;
|
const seen = {};
|
||||||
setScreenLayout(outputName, result.node);
|
const remaining = [];
|
||||||
|
let removed = false;
|
||||||
|
collectLeaves(rootNode, leaves);
|
||||||
|
|
||||||
|
for (let i = 0; i < leaves.length; i++) {
|
||||||
|
const leafWindow = leaves[i].win;
|
||||||
|
const leafKey = windowKey(leafWindow);
|
||||||
|
if (leafKey === key) {
|
||||||
|
removed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!leafKey || seen[leafKey] || !isTileable(leafWindow)) continue;
|
||||||
|
if (!leafWindow.output || leafWindow.output.name !== outputName) continue;
|
||||||
|
seen[leafKey] = true;
|
||||||
|
remaining.push(leafWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!removed) return false;
|
||||||
|
setStableLayout(outputName, remaining);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,16 @@ require_line "$effect_qml" "showPreview(\"insert\""
|
||||||
require_line "$effect_qml" "showPreview(\"float\""
|
require_line "$effect_qml" "showPreview(\"float\""
|
||||||
require_line "$effect_qml" "showPreview(\"restore\""
|
require_line "$effect_qml" "showPreview(\"restore\""
|
||||||
|
|
||||||
|
require_line "$tiling_script" "readonly property real stablePrimaryRatio: 0.58"
|
||||||
|
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)"
|
||||||
|
|
||||||
|
reject_line "$tiling_script" "targetKey = lastLeafKey(rootNode)"
|
||||||
|
reject_line "$tiling_script" "setScreenLayout(outputName, splitLeaf(rootNode, targetKey"
|
||||||
reject_line "$tiling_script" "KWinComponents.Workspace.showOutline(dragOutlineRect)"
|
reject_line "$tiling_script" "KWinComponents.Workspace.showOutline(dragOutlineRect)"
|
||||||
reject_line "$tiling_script" "KWinComponents.Workspace.hideOutline()"
|
reject_line "$tiling_script" "KWinComponents.Workspace.hideOutline()"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue