Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57c4643098 | ||
|
|
e92563b424 | ||
|
|
671326bdd7 | ||
|
|
5e9db7d2cd | ||
|
|
b447eacdfd | ||
|
|
94f6e6f33b | ||
|
|
85b0221220 | ||
|
|
1894b055f7 | ||
|
|
05f7550a3b | ||
|
|
a04f629de0 | ||
|
|
4bda4d0d7c | ||
|
|
8bf076948a | ||
|
|
04bd85a287 | ||
|
|
b8cf677084 | ||
|
|
e67e6f4d62 | ||
|
|
bdf62b65e4 | ||
|
|
3238d70772 | ||
|
|
8382696d01 | ||
|
|
18470b391f | ||
|
|
ed8ec7c794 | ||
|
|
fce442c25d | ||
|
|
81ef0e0442 | ||
|
|
0fbb0fe90e | ||
|
|
b12b70a294 | ||
|
|
0a3ba5c963 | ||
|
|
fa17b1fcc2 | ||
|
|
68d2c5bbd8 | ||
|
|
5c8da41647 | ||
|
|
9e808f99c9 | ||
|
|
29fa65613e | ||
|
|
9c1592b626 | ||
|
|
dec4281bb9 | ||
|
|
41facafac7 | ||
|
|
0635e20622 | ||
|
|
454a14724d | ||
|
|
0fff1ce837 | ||
|
|
ee8bac5a42 | ||
|
|
dba5e07a86 | ||
|
|
e2a5625d41 | ||
|
|
6e9edad39d | ||
|
|
0266cde2f1 | ||
|
|
f0e662de37 | ||
|
|
e5c9b52370 | ||
|
|
83ac2506cf | ||
|
|
d8eec7a881 | ||
|
|
cb66a26394 | ||
|
|
414bfc8518 | ||
|
|
81a82cbfde | ||
|
|
9318799a82 | ||
|
|
2f93e3aa8f | ||
|
|
e1263dd544 | ||
|
|
b614fd4481 | ||
|
|
13d6f39bf2 | ||
|
|
3b103841c2 | ||
|
|
9ec9e8e62d | ||
|
|
208ef7d9fb | ||
|
|
b7610be193 | ||
|
|
ed9d4320ae | ||
|
|
e3a6b1ad91 | ||
|
|
31a56b8f24 | ||
|
|
a65a62f396 | ||
|
|
88b976b252 | ||
|
|
4bdc031d7b | ||
|
|
0cf395d2e1 |
13
Makefile
13
Makefile
@@ -1,8 +1,10 @@
|
||||
.PHONY: *
|
||||
|
||||
TSC_SCRIPT_FLAGS = --lib es2020 ./src/extern.d.ts
|
||||
|
||||
config:
|
||||
mkdir -p ./package/contents/config
|
||||
tsc ./src/config/definition.ts ./configgen/kcfg.ts --outFile /dev/stdout | node - > ./package/contents/config/main.xml
|
||||
tsc ${TSC_SCRIPT_FLAGS} ./src/config/definition.ts ./generators/config/kcfg.ts --outFile /dev/stdout | node - > ./package/contents/config/main.xml
|
||||
|
||||
build:
|
||||
tsc --outFile ./package/contents/code/main.js
|
||||
@@ -18,3 +20,12 @@ package:
|
||||
|
||||
logs:
|
||||
journalctl -t kwin_x11 -g '^qml:|^file://.*karousel' -f
|
||||
|
||||
docs-key-bindings-bbcode:
|
||||
@tsc ${TSC_SCRIPT_FLAGS} ./src/keyBindings/definition.ts ./generators/docs/keyBindings.ts ./generators/docs/keyBindingsBbcode.ts --outFile /dev/stdout | node -
|
||||
|
||||
docs-key-bindings-table:
|
||||
@tsc ${TSC_SCRIPT_FLAGS} ./src/keyBindings/definition.ts ./generators/docs/keyBindings.ts ./generators/docs/keyBindingsTable.ts --outFile /dev/stdout | node -
|
||||
|
||||
docs-key-bindings-fmt:
|
||||
@tsc ${TSC_SCRIPT_FLAGS} ./src/keyBindings/definition.ts ./generators/docs/keyBindings.ts ./generators/docs/keyBindingsFmt.ts --outFile /dev/stdout | node -
|
||||
|
||||
36
README.md
36
README.md
@@ -20,3 +20,39 @@ Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM) an
|
||||
- Doesn't support multiple screens
|
||||
- Doesn't support windows on all desktops
|
||||
- Doesn't support windows on multiple activities
|
||||
|
||||
## Key bindings
|
||||
The key bindings can be configured in KDE System Settings among KWin's own keyboard shortcuts.
|
||||
Here's the default ones:
|
||||
| Shortcut | Action |
|
||||
| --- | --- |
|
||||
| Meta+Space | Toggle floating |
|
||||
| Meta+A | Move focus left |
|
||||
| Meta+D | Move focus right |
|
||||
| Meta+W | Move focus up |
|
||||
| Meta+S | Move focus down |
|
||||
| Meta+Home | Move focus to start |
|
||||
| Meta+End | Move focus to end |
|
||||
| Meta+Shift+A | Move window left (Moves window out of and into columns) |
|
||||
| Meta+Shift+D | Move window right (Moves window out of and into columns) |
|
||||
| Meta+Shift+W | Move window up |
|
||||
| Meta+Shift+S | Move window down |
|
||||
| Meta+Shift+Home | Move window to start |
|
||||
| Meta+Shift+End | Move window to end |
|
||||
| Meta+X | Toggle stacked layout for focused column |
|
||||
| Meta+Ctrl+Shift+A | Move column left |
|
||||
| Meta+Ctrl+Shift+D | Move column right |
|
||||
| Meta+Ctrl+Shift+Home | Move column to start |
|
||||
| Meta+Ctrl+Shift+End | Move column to end |
|
||||
| Meta+Ctrl++ | Increase column width |
|
||||
| Meta+Ctrl+- | Decrease column width |
|
||||
| Meta+Alt+Return | Center focused window (Scrolls so that the focused window is centered in the screen) |
|
||||
| Meta+Alt+A | Scroll one column to the left |
|
||||
| Meta+Alt+D | Scroll one column to the right |
|
||||
| Meta+Alt+Home | Scroll to start |
|
||||
| Meta+Alt+End | Scroll to end |
|
||||
| Meta+[N] | Move focus to column N |
|
||||
| Meta+Shift+[N] | Move window to column N (Requires manual remapping according to your keyboard layout, e.g. Meta+Shift+1 -> Meta+!) |
|
||||
| Meta+Ctrl+Shift+[N] | Move column to position N (Requires manual remapping according to your keyboard layout, e.g. Meta+Ctrl+Shift+1 -> Meta+Ctrl+!) |
|
||||
| Meta+Ctrl+Shift+F[N] | Move column to desktop N |
|
||||
| Meta+Ctrl+Shift+Alt+F[N] | Move this and all following columns to desktop N |
|
||||
|
||||
67
generators/docs/keyBindings.ts
Normal file
67
generators/docs/keyBindings.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
interface KeyBinding {
|
||||
name: string;
|
||||
description: string;
|
||||
comment?: string;
|
||||
defaultKeySequence: string;
|
||||
action: string;
|
||||
}
|
||||
|
||||
interface NumKeyBinding {
|
||||
name: string;
|
||||
description: string;
|
||||
comment?: string;
|
||||
defaultModifiers: string;
|
||||
fKeys: boolean;
|
||||
action: string;
|
||||
}
|
||||
|
||||
function formatComment(comment: string | undefined) {
|
||||
return comment === undefined ? "" : ` (${comment})`;
|
||||
}
|
||||
|
||||
function printCols(...columns: (string[] | string)[]) {
|
||||
const nCols = columns.length;
|
||||
if (nCols == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nRows = Math.min(...columns.filter(
|
||||
(column: string[] | string) => column instanceof Array
|
||||
).map(
|
||||
(column: string[] | string) => column.length
|
||||
));
|
||||
if (nRows == Infinity) {
|
||||
// we only have single string columns
|
||||
nRows = 1;
|
||||
}
|
||||
|
||||
const colWidths = columns.map(
|
||||
(column: string[] | string) => {
|
||||
if (column instanceof Array) {
|
||||
return Math.max(...column.map(
|
||||
(cell: string) => cell.length
|
||||
))
|
||||
} else {
|
||||
return column.length;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function getCell(col: number, row: number) {
|
||||
const column = columns[col];
|
||||
const cell = column instanceof Array ? column[row] : column;
|
||||
if (col < nCols-1) {
|
||||
return cell.padEnd(colWidths[col]);
|
||||
} else {
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
for (let row = 0; row < nRows; row++) {
|
||||
let line = "";
|
||||
for (let col = 0; col < nCols; col++) {
|
||||
line += getCell(col, row);
|
||||
}
|
||||
console.log(line);
|
||||
}
|
||||
}
|
||||
12
generators/docs/keyBindingsBbcode.ts
Normal file
12
generators/docs/keyBindingsBbcode.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
console.log(`[list]`);
|
||||
|
||||
for (const binding of keyBindings) {
|
||||
console.log(` [*] ${binding.defaultKeySequence} — ${binding.description}${formatComment(binding.comment)}`);
|
||||
}
|
||||
|
||||
for (const binding of numKeyBindings) {
|
||||
const numPrefix = binding.fKeys ? "F" : "";
|
||||
console.log(` [*] ${binding.defaultModifiers}+${numPrefix}[N] — ${binding.description}N${formatComment(binding.comment)}`);
|
||||
}
|
||||
|
||||
console.log(`[/list]`);
|
||||
14
generators/docs/keyBindingsFmt.ts
Normal file
14
generators/docs/keyBindingsFmt.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const colLeft = [
|
||||
...keyBindings.map((binding: KeyBinding) => binding.defaultKeySequence),
|
||||
...numKeyBindings.map((binding: NumKeyBinding) => {
|
||||
const numPrefix = binding.fKeys ? "F" : "";
|
||||
return `${binding.defaultModifiers}+${numPrefix}[N]`;
|
||||
}),
|
||||
];
|
||||
|
||||
const colRight = [
|
||||
...keyBindings.map((binding: KeyBinding) => `${binding.description}${formatComment(binding.comment)}`),
|
||||
...numKeyBindings.map((binding: NumKeyBinding) => `${binding.description}N${formatComment(binding.comment)}`),
|
||||
];
|
||||
|
||||
printCols(colLeft, " ", colRight);
|
||||
18
generators/docs/keyBindingsTable.ts
Normal file
18
generators/docs/keyBindingsTable.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
const colLeft = [
|
||||
"Shortcut",
|
||||
"---",
|
||||
...keyBindings.map((binding: KeyBinding) => binding.defaultKeySequence),
|
||||
...numKeyBindings.map((binding: NumKeyBinding) => {
|
||||
const numPrefix = binding.fKeys ? "F" : "";
|
||||
return `${binding.defaultModifiers}+${numPrefix}[N]`;
|
||||
}),
|
||||
];
|
||||
|
||||
const colRight = [
|
||||
"Action",
|
||||
"---",
|
||||
...keyBindings.map((binding: KeyBinding) => `${binding.description}${formatComment(binding.comment)}`),
|
||||
...numKeyBindings.map((binding: NumKeyBinding) => `${binding.description}N${formatComment(binding.comment)}`),
|
||||
];
|
||||
|
||||
printCols("| ", colLeft, " | ", colRight, " |");
|
||||
@@ -209,6 +209,14 @@
|
||||
</item>
|
||||
|
||||
<item row="8" column="1">
|
||||
<widget class="QCheckBox" name="kcfg_untileOnDrag">
|
||||
<property name="text">
|
||||
<string>Un-tile windows by dragging them</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
|
||||
<property name="text">
|
||||
<string>Stack columns by default</string>
|
||||
@@ -216,7 +224,15 @@
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
<item row="9" column="0" colspan="2">
|
||||
<item row="10" column="1">
|
||||
<widget class="QCheckBox" name="kcfg_resizeNeighborColumn">
|
||||
<property name="text">
|
||||
<string>Resize neighbor column on edge resize</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
<item row="11" column="0" colspan="2">
|
||||
<spacer name="bottomSpacer_tab_general">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}],
|
||||
"Id": "karousel",
|
||||
"ServiceTypes": ["KWin/Script"],
|
||||
"Version": "0.1",
|
||||
"Version": "0.3.1",
|
||||
"License": "GPLv3",
|
||||
"Website": "https://github.com/peterfajdiga/karousel",
|
||||
"BugReportUrl": "https://github.com/peterfajdiga/karousel/issues"
|
||||
|
||||
335
src/Actions.ts
Normal file
335
src/Actions.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
module Actions {
|
||||
export function init(world: World, config: Config) {
|
||||
return {
|
||||
focusLeft: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const prevColumn = grid.getPrevColumn(column);
|
||||
if (prevColumn === null) {
|
||||
return;
|
||||
}
|
||||
prevColumn.focus();
|
||||
});
|
||||
},
|
||||
|
||||
focusRight: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const nextColumn = grid.getNextColumn(column);
|
||||
if (nextColumn === null) {
|
||||
return;
|
||||
}
|
||||
nextColumn.focus();
|
||||
});
|
||||
},
|
||||
|
||||
focusUp: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const prevWindow = column.getPrevWindow(window);
|
||||
if (prevWindow === null) {
|
||||
return;
|
||||
}
|
||||
prevWindow.focus();
|
||||
});
|
||||
},
|
||||
|
||||
focusDown: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const nextWindow = column.getNextWindow(window);
|
||||
if (nextWindow === null) {
|
||||
return;
|
||||
}
|
||||
nextWindow.focus();
|
||||
});
|
||||
},
|
||||
|
||||
focusStart: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const firstColumn = grid.getFirstColumn();
|
||||
if (firstColumn === null) {
|
||||
return;
|
||||
}
|
||||
firstColumn.focus();
|
||||
grid.container.arrange();
|
||||
},
|
||||
|
||||
focusEnd: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const lastColumn = grid.getLastColumn();
|
||||
if (lastColumn === null) {
|
||||
return;
|
||||
}
|
||||
lastColumn.focus();
|
||||
grid.container.arrange();
|
||||
},
|
||||
|
||||
windowMoveLeft: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
if (column.getWindowCount() === 1) {
|
||||
// move from own column into existing column
|
||||
const prevColumn = grid.getPrevColumn(column);
|
||||
if (prevColumn === null) {
|
||||
return;
|
||||
}
|
||||
window.moveToColumn(prevColumn);
|
||||
grid.container.onGridReordered();
|
||||
} else {
|
||||
// move from shared column into own column
|
||||
const newColumn = new Column(grid, grid.getPrevColumn(column));
|
||||
window.moveToColumn(newColumn);
|
||||
}
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveRight: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
if (column.getWindowCount() === 1) {
|
||||
// move from own column into existing column
|
||||
const nextColumn = grid.getNextColumn(column);
|
||||
if (nextColumn === null) {
|
||||
return;
|
||||
}
|
||||
window.moveToColumn(nextColumn);
|
||||
grid.container.onGridReordered();
|
||||
} else {
|
||||
// move from shared column into own column
|
||||
const newColumn = new Column(grid, column);
|
||||
window.moveToColumn(newColumn);
|
||||
}
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveUp: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
column.moveWindowUp(window);
|
||||
grid.container.arrange(); // TODO (optimization): only arrange moved windows
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveDown: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
column.moveWindowDown(window);
|
||||
grid.container.arrange(); // TODO (optimization): only arrange moved windows
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveStart: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const newColumn = new Column(grid, null);
|
||||
window.moveToColumn(newColumn);
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveEnd: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const newColumn = new Column(grid, grid.getLastColumn());
|
||||
window.moveToColumn(newColumn);
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
windowToggleFloating: () => {
|
||||
const kwinClient = workspace.activeClient;
|
||||
world.toggleFloatingClient(kwinClient);
|
||||
},
|
||||
|
||||
columnMoveLeft: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
grid.moveColumnLeft(column);
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveRight: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
grid.moveColumnRight(column);
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveStart: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
column.moveAfter(null);
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveEnd: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
column.moveAfter(grid.getLastColumn());
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnToggleStacked: () => {
|
||||
world.doIfTiledFocused(false, (window, column, grid) => {
|
||||
column.toggleStacked();
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnWidthIncrease: () => {
|
||||
world.doIfTiledFocused(false, (window, column, grid) => {
|
||||
grid.increaseColumnWidth(column);
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnWidthDecrease: () => {
|
||||
world.doIfTiledFocused(false, (window, column, grid) => {
|
||||
grid.decreaseColumnWidth(column);
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
gridScrollLeft: () => {
|
||||
gridScroll(world, -config.manualScrollStep);
|
||||
},
|
||||
|
||||
gridScrollRight: () => {
|
||||
gridScroll(world, config.manualScrollStep);
|
||||
},
|
||||
|
||||
gridScrollStart: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const firstColumn = grid.getFirstColumn();
|
||||
if (firstColumn === null) {
|
||||
return;
|
||||
}
|
||||
grid.container.scrollToColumn(firstColumn);
|
||||
grid.container.arrange();
|
||||
},
|
||||
|
||||
gridScrollEnd: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const lastColumn = grid.getLastColumn();
|
||||
if (lastColumn === null) {
|
||||
return;
|
||||
}
|
||||
grid.container.scrollToColumn(lastColumn);
|
||||
grid.container.arrange();
|
||||
},
|
||||
|
||||
gridScrollFocused: () => {
|
||||
const focusedWindow = world.getFocusedWindow();
|
||||
if (focusedWindow === null) {
|
||||
return;
|
||||
}
|
||||
const column = focusedWindow.column;
|
||||
const grid = column.grid;
|
||||
grid.container.scrollCenterColumn(column);
|
||||
grid.container.arrange();
|
||||
},
|
||||
|
||||
gridScrollLeftColumn: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const column = grid.getLeftmostVisibleColumn(grid.container.getCurrentScrollPos(), true);
|
||||
if (column === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevColumn = grid.getPrevColumn(column);
|
||||
if (prevColumn === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
grid.container.scrollToColumn(prevColumn);
|
||||
grid.container.arrange();
|
||||
},
|
||||
|
||||
gridScrollRightColumn: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const column = grid.getRightmostVisibleColumn(grid.container.getCurrentScrollPos(), true);
|
||||
if (column === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextColumn = grid.getNextColumn(column);
|
||||
if (nextColumn === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
grid.container.scrollToColumn(nextColumn);
|
||||
grid.container.arrange();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function initNum(world: World) {
|
||||
return {
|
||||
focusColumn: (columnIndex: number) => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||
if (targetColumn === null) {
|
||||
return null;
|
||||
}
|
||||
targetColumn.focus();
|
||||
},
|
||||
|
||||
windowMoveToColumn: (columnIndex: number) => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||
if (targetColumn === null) {
|
||||
return null;
|
||||
}
|
||||
window.moveToColumn(targetColumn);
|
||||
grid.container.onGridReordered();
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveToColumn: (columnIndex: number) => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||
if (targetColumn === null || targetColumn === column) {
|
||||
return null;
|
||||
}
|
||||
if (targetColumn.isAfter(column)) {
|
||||
column.moveAfter(targetColumn);
|
||||
} else {
|
||||
column.moveAfter(grid.getPrevColumn(targetColumn));
|
||||
}
|
||||
grid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveToDesktop: (desktopIndex: number) => {
|
||||
world.doIfTiledFocused(true, (window, column, oldGrid) => {
|
||||
const desktopNumber = desktopIndex + 1;
|
||||
const newGrid = world.getGridInCurrentActivity(desktopNumber);
|
||||
if (newGrid === null || newGrid === oldGrid) {
|
||||
return;
|
||||
}
|
||||
column.moveToGrid(newGrid, newGrid.getLastColumn());
|
||||
oldGrid.container.arrange();
|
||||
newGrid.container.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
tailMoveToDesktop: (desktopIndex: number) => {
|
||||
world.doIfTiledFocused(true, (window, column, oldGrid) => {
|
||||
const desktopNumber = desktopIndex + 1;
|
||||
const newGrid = world.getGridInCurrentActivity(desktopNumber);
|
||||
if (newGrid === null || newGrid === oldGrid) {
|
||||
return;
|
||||
}
|
||||
oldGrid.evacuateTail(newGrid, column);
|
||||
oldGrid.container.arrange();
|
||||
newGrid.container.arrange();
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function gridScroll(world: World, amount: number) {
|
||||
const scrollAmount = amount;
|
||||
const grid = world.getCurrentGrid();
|
||||
grid.container.adjustScroll(scrollAmount, false);
|
||||
grid.container.arrange();
|
||||
}
|
||||
|
||||
export type Config = {
|
||||
manualScrollStep: number,
|
||||
}
|
||||
}
|
||||
338
src/actions.ts
338
src/actions.ts
@@ -1,338 +0,0 @@
|
||||
function initActions(world: World) {
|
||||
return {
|
||||
focusLeft: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const prevColumn = grid.getPrevColumn(column);
|
||||
if (prevColumn === null) {
|
||||
return;
|
||||
}
|
||||
prevColumn.focus();
|
||||
});
|
||||
},
|
||||
|
||||
focusRight: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const nextColumn = grid.getNextColumn(column);
|
||||
if (nextColumn === null) {
|
||||
return;
|
||||
}
|
||||
nextColumn.focus();
|
||||
});
|
||||
},
|
||||
|
||||
focusUp: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const prevWindow = column.getPrevWindow(window);
|
||||
if (prevWindow === null) {
|
||||
return;
|
||||
}
|
||||
prevWindow.focus();
|
||||
});
|
||||
},
|
||||
|
||||
focusDown: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const nextWindow = column.getNextWindow(window);
|
||||
if (nextWindow === null) {
|
||||
return;
|
||||
}
|
||||
nextWindow.focus();
|
||||
});
|
||||
},
|
||||
|
||||
focusStart: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const firstColumn = grid.getFirstColumn();
|
||||
if (firstColumn === null) {
|
||||
return;
|
||||
}
|
||||
firstColumn.focus();
|
||||
grid.arrange();
|
||||
},
|
||||
|
||||
focusEnd: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const lastColumn = grid.getLastColumn();
|
||||
if (lastColumn === null) {
|
||||
return;
|
||||
}
|
||||
lastColumn.focus();
|
||||
grid.arrange();
|
||||
},
|
||||
|
||||
windowMoveLeft: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
if (column.getWindowCount() === 1) {
|
||||
// move from own column into existing column
|
||||
const prevColumn = grid.getPrevColumn(column);
|
||||
if (prevColumn === null) {
|
||||
return;
|
||||
}
|
||||
window.moveToColumn(prevColumn);
|
||||
grid.autoAdjustScroll();
|
||||
} else {
|
||||
// move from shared column into own column
|
||||
const newColumn = new Column(grid, grid.getPrevColumn(column));
|
||||
window.moveToColumn(newColumn);
|
||||
}
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveRight: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
if (column.getWindowCount() === 1) {
|
||||
// move from own column into existing column
|
||||
const nextColumn = grid.getNextColumn(column);
|
||||
if (nextColumn === null) {
|
||||
return;
|
||||
}
|
||||
window.moveToColumn(nextColumn);
|
||||
grid.autoAdjustScroll();
|
||||
} else {
|
||||
// move from shared column into own column
|
||||
const newColumn = new Column(grid, column);
|
||||
window.moveToColumn(newColumn);
|
||||
}
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveUp: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
column.moveWindowUp(window);
|
||||
grid.arrange(); // TODO (optimization): only arrange moved windows
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveDown: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
column.moveWindowDown(window);
|
||||
grid.arrange(); // TODO (optimization): only arrange moved windows
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveStart: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const newColumn = new Column(grid, null);
|
||||
window.moveToColumn(newColumn);
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
windowMoveEnd: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const newColumn = new Column(grid, grid.getLastColumn());
|
||||
window.moveToColumn(newColumn);
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
windowExpand: () => {
|
||||
world.doIfTiledFocused(false, (window, column, grid) => {
|
||||
column.toggleStacked();
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
windowToggleFloating: () => {
|
||||
const kwinClient = workspace.activeClient;
|
||||
world.toggleFloatingClient(kwinClient);
|
||||
},
|
||||
|
||||
columnMoveLeft: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
grid.moveColumnLeft(column);
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveRight: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
grid.moveColumnRight(column);
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveStart: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
column.moveAfter(null);
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveEnd: () => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
column.moveAfter(grid.getLastColumn());
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnExpand: () => {
|
||||
world.doIfTiledFocused(false, (window, column, grid) => {
|
||||
column.expand();
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
gridScrollLeft: () => {
|
||||
gridScroll(world, -world.config.manualScrollStep);
|
||||
},
|
||||
|
||||
gridScrollRight: () => {
|
||||
gridScroll(world, world.config.manualScrollStep);
|
||||
},
|
||||
|
||||
gridScrollStart: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const firstColumn = grid.getFirstColumn();
|
||||
if (firstColumn === null) {
|
||||
return;
|
||||
}
|
||||
grid.scrollToColumn(firstColumn);
|
||||
grid.arrange();
|
||||
},
|
||||
|
||||
gridScrollEnd: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const lastColumn = grid.getLastColumn();
|
||||
if (lastColumn === null) {
|
||||
return;
|
||||
}
|
||||
grid.scrollToColumn(lastColumn);
|
||||
grid.arrange();
|
||||
},
|
||||
|
||||
gridScrollFocused: () => {
|
||||
const focusedWindow = world.getFocusedWindow();
|
||||
if (focusedWindow === null) {
|
||||
return;
|
||||
}
|
||||
const column = focusedWindow.column;
|
||||
const grid = column.grid;
|
||||
grid.scrollToColumn(column);
|
||||
grid.arrange();
|
||||
},
|
||||
|
||||
gridScrollLeftColumn: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const column = grid.getLeftmostVisibleColumn(true);
|
||||
if (column === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevColumn = grid.getPrevColumn(column);
|
||||
if (prevColumn === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
grid.scrollToColumn(prevColumn);
|
||||
grid.arrange();
|
||||
},
|
||||
|
||||
gridScrollRightColumn: () => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const column = grid.getRightmostVisibleColumn(true);
|
||||
if (column === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextColumn = grid.getNextColumn(column);
|
||||
if (nextColumn === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
grid.scrollToColumn(nextColumn);
|
||||
grid.arrange();
|
||||
},
|
||||
|
||||
focusColumn: (columnIndex: number) => {
|
||||
const grid = world.getCurrentGrid();
|
||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||
if (targetColumn === null) {
|
||||
return null;
|
||||
}
|
||||
targetColumn.focus();
|
||||
},
|
||||
|
||||
windowMoveToColumn: (columnIndex: number) => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||
if (targetColumn === null) {
|
||||
return null;
|
||||
}
|
||||
window.moveToColumn(targetColumn);
|
||||
grid.autoAdjustScroll();
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveToColumn: (columnIndex: number) => {
|
||||
world.doIfTiledFocused(true, (window, column, grid) => {
|
||||
const targetColumn = grid.getColumnAtIndex(columnIndex);
|
||||
if (targetColumn === null || targetColumn === column) {
|
||||
return null;
|
||||
}
|
||||
if (targetColumn.isAfter(column)) {
|
||||
column.moveAfter(targetColumn);
|
||||
} else {
|
||||
column.moveAfter(grid.getPrevColumn(targetColumn));
|
||||
}
|
||||
grid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
columnMoveToDesktop: (desktopIndex: number) => {
|
||||
world.doIfTiledFocused(true, (window, column, oldGrid) => {
|
||||
const desktopNumber = desktopIndex + 1;
|
||||
const newGrid = world.getGridInCurrentActivity(desktopNumber);
|
||||
if (newGrid === null || newGrid === oldGrid) {
|
||||
return;
|
||||
}
|
||||
column.moveToGrid(newGrid, newGrid.getLastColumn());
|
||||
oldGrid.arrange();
|
||||
newGrid.arrange();
|
||||
});
|
||||
},
|
||||
|
||||
tailMoveToDesktop: (desktopIndex: number) => {
|
||||
world.doIfTiledFocused(true, (window, column, oldGrid) => {
|
||||
const desktopNumber = desktopIndex + 1;
|
||||
const newGrid = world.getGridInCurrentActivity(desktopNumber);
|
||||
if (newGrid === null || newGrid === oldGrid) {
|
||||
return;
|
||||
}
|
||||
oldGrid.evacuateTail(newGrid, column);
|
||||
oldGrid.arrange();
|
||||
newGrid.arrange();
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function gridScroll(world: World, amount: number) {
|
||||
const scrollAmount = amount;
|
||||
const grid = world.getCurrentGrid();
|
||||
grid.adjustScroll(scrollAmount, false);
|
||||
grid.arrange();
|
||||
}
|
||||
|
||||
function canTileEver(kwinClient: AbstractClient) {
|
||||
return kwinClient.resizeable;
|
||||
}
|
||||
|
||||
function canTileNow(kwinClient: AbstractClient) {
|
||||
return canTileEver(kwinClient) && !kwinClient.minimized && kwinClient.desktop > 0 && kwinClient.activities.length === 1;
|
||||
}
|
||||
|
||||
function makeTileable(kwinClient: AbstractClient) {
|
||||
if (kwinClient.minimized) {
|
||||
kwinClient.minimized = false;
|
||||
}
|
||||
if (kwinClient.desktop <= 0) {
|
||||
kwinClient.desktop = workspace.currentDesktop;
|
||||
}
|
||||
if (kwinClient.activities.length !== 1) {
|
||||
kwinClient.activities = [workspace.currentActivity];
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ type Config = {
|
||||
gapsInnerVertical: number,
|
||||
overscroll: number,
|
||||
manualScrollStep: number,
|
||||
untileOnDrag: boolean,
|
||||
stackColumnsByDefault: boolean,
|
||||
resizeNeighborColumn: boolean,
|
||||
windowRules: string,
|
||||
}
|
||||
|
||||
@@ -88,11 +88,21 @@ const configDef = [
|
||||
"type": "UInt",
|
||||
"default": 200
|
||||
},
|
||||
{
|
||||
"name": "untileOnDrag",
|
||||
"type": "Bool",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "stackColumnsByDefault",
|
||||
"type": "Bool",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "resizeNeighborColumn",
|
||||
"type": "Bool",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "windowRules",
|
||||
"type": "String",
|
||||
|
||||
12
src/extern.d.ts
vendored
12
src/extern.d.ts
vendored
@@ -1,9 +1,9 @@
|
||||
const qmlBase;
|
||||
const console;
|
||||
const KWin;
|
||||
const Qt;
|
||||
const workspace;
|
||||
const options;
|
||||
declare const qmlBase;
|
||||
declare const console;
|
||||
declare const KWin;
|
||||
declare const Qt;
|
||||
declare const workspace;
|
||||
declare const options;
|
||||
|
||||
type AbstractClient = any;
|
||||
type TopLevel = any;
|
||||
|
||||
207
src/keyBindings/definition.ts
Normal file
207
src/keyBindings/definition.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
const keyBindings: KeyBinding[] = [
|
||||
{
|
||||
"name": "window-toggle-floating",
|
||||
"description": "Toggle floating",
|
||||
"defaultKeySequence": "Meta+Space",
|
||||
"action": "windowToggleFloating",
|
||||
},
|
||||
{
|
||||
"name": "focus-left",
|
||||
"description": "Move focus left",
|
||||
"defaultKeySequence": "Meta+A",
|
||||
"action": "focusLeft",
|
||||
},
|
||||
{
|
||||
"name": "focus-right",
|
||||
"description": "Move focus right",
|
||||
"defaultKeySequence": "Meta+D",
|
||||
"action": "focusRight",
|
||||
},
|
||||
{
|
||||
"name": "focus-up",
|
||||
"description": "Move focus up",
|
||||
"defaultKeySequence": "Meta+W",
|
||||
"action": "focusUp",
|
||||
},
|
||||
{
|
||||
"name": "focus-down",
|
||||
"description": "Move focus down",
|
||||
"defaultKeySequence": "Meta+S",
|
||||
"action": "focusDown",
|
||||
},
|
||||
{
|
||||
"name": "focus-start",
|
||||
"description": "Move focus to start",
|
||||
"defaultKeySequence": "Meta+Home",
|
||||
"action": "focusStart",
|
||||
},
|
||||
{
|
||||
"name": "focus-end",
|
||||
"description": "Move focus to end",
|
||||
"defaultKeySequence": "Meta+End",
|
||||
"action": "focusEnd",
|
||||
},
|
||||
{
|
||||
"name": "window-move-left",
|
||||
"description": "Move window left",
|
||||
"comment": "Moves window out of and into columns",
|
||||
"defaultKeySequence": "Meta+Shift+A",
|
||||
"action": "windowMoveLeft",
|
||||
},
|
||||
{
|
||||
"name": "window-move-right",
|
||||
"description": "Move window right",
|
||||
"comment": "Moves window out of and into columns",
|
||||
"defaultKeySequence": "Meta+Shift+D",
|
||||
"action": "windowMoveRight",
|
||||
},
|
||||
{
|
||||
"name": "window-move-up",
|
||||
"description": "Move window up",
|
||||
"defaultKeySequence": "Meta+Shift+W",
|
||||
"action": "windowMoveUp",
|
||||
},
|
||||
{
|
||||
"name": "window-move-down",
|
||||
"description": "Move window down",
|
||||
"defaultKeySequence": "Meta+Shift+S",
|
||||
"action": "windowMoveDown",
|
||||
},
|
||||
{
|
||||
"name": "window-move-start",
|
||||
"description": "Move window to start",
|
||||
"defaultKeySequence": "Meta+Shift+Home",
|
||||
"action": "windowMoveStart",
|
||||
},
|
||||
{
|
||||
"name": "window-move-end",
|
||||
"description": "Move window to end",
|
||||
"defaultKeySequence": "Meta+Shift+End",
|
||||
"action": "windowMoveEnd",
|
||||
},
|
||||
{
|
||||
"name": "column-toggle-stacked",
|
||||
"description": "Toggle stacked layout for focused column",
|
||||
"defaultKeySequence": "Meta+X",
|
||||
"action": "columnToggleStacked",
|
||||
},
|
||||
{
|
||||
"name": "column-move-left",
|
||||
"description": "Move column left",
|
||||
"defaultKeySequence": "Meta+Ctrl+Shift+A",
|
||||
"action": "columnMoveLeft",
|
||||
},
|
||||
{
|
||||
"name": "column-move-right",
|
||||
"description": "Move column right",
|
||||
"defaultKeySequence": "Meta+Ctrl+Shift+D",
|
||||
"action": "columnMoveRight",
|
||||
},
|
||||
{
|
||||
"name": "column-move-start",
|
||||
"description": "Move column to start",
|
||||
"defaultKeySequence": "Meta+Ctrl+Shift+Home",
|
||||
"action": "columnMoveStart",
|
||||
},
|
||||
{
|
||||
"name": "column-move-end",
|
||||
"description": "Move column to end",
|
||||
"defaultKeySequence": "Meta+Ctrl+Shift+End",
|
||||
"action": "columnMoveEnd",
|
||||
},
|
||||
{
|
||||
"name": "column-width-increase",
|
||||
"description": "Increase column width",
|
||||
"defaultKeySequence": "Meta+Ctrl++",
|
||||
"action": "columnWidthIncrease",
|
||||
},
|
||||
{
|
||||
"name": "column-width-decrease",
|
||||
"description": "Decrease column width",
|
||||
"defaultKeySequence": "Meta+Ctrl+-",
|
||||
"action": "columnWidthDecrease",
|
||||
},
|
||||
{
|
||||
"name": "grid-scroll-focused",
|
||||
"description": "Center focused window",
|
||||
"comment": "Scrolls so that the focused window is centered in the screen",
|
||||
"defaultKeySequence": "Meta+Alt+Return",
|
||||
"action": "gridScrollFocused",
|
||||
},
|
||||
{
|
||||
"name": "grid-scroll-left-column",
|
||||
"description": "Scroll one column to the left",
|
||||
"defaultKeySequence": "Meta+Alt+A",
|
||||
"action": "gridScrollLeftColumn",
|
||||
},
|
||||
{
|
||||
"name": "grid-scroll-right-column",
|
||||
"description": "Scroll one column to the right",
|
||||
"defaultKeySequence": "Meta+Alt+D",
|
||||
"action": "gridScrollRightColumn",
|
||||
},
|
||||
{
|
||||
"name": "grid-scroll-left",
|
||||
"description": "Scroll left",
|
||||
"defaultKeySequence": "Meta+Alt+PgUp",
|
||||
"action": "gridScrollLeft",
|
||||
},
|
||||
{
|
||||
"name": "grid-scroll-right",
|
||||
"description": "Scroll right",
|
||||
"defaultKeySequence": "Meta+Alt+PgDown",
|
||||
"action": "gridScrollRight",
|
||||
},
|
||||
{
|
||||
"name": "grid-scroll-start",
|
||||
"description": "Scroll to start",
|
||||
"defaultKeySequence": "Meta+Alt+Home",
|
||||
"action": "gridScrollStart",
|
||||
},
|
||||
{
|
||||
"name": "grid-scroll-end",
|
||||
"description": "Scroll to end",
|
||||
"defaultKeySequence": "Meta+Alt+End",
|
||||
"action": "gridScrollEnd",
|
||||
},
|
||||
];
|
||||
|
||||
const numKeyBindings: NumKeyBinding[] = [
|
||||
{
|
||||
"name": "focus-",
|
||||
"description": "Move focus to column ",
|
||||
"defaultModifiers": "Meta",
|
||||
"fKeys": false,
|
||||
"action": "focusColumn",
|
||||
},
|
||||
{
|
||||
"name": "window-move-to-column-",
|
||||
"description": "Move window to column ",
|
||||
"comment": "Requires manual remapping according to your keyboard layout, e.g. Meta+Shift+1 -> Meta+!",
|
||||
"defaultModifiers": "Meta+Shift",
|
||||
"fKeys": false,
|
||||
"action": "windowMoveToColumn",
|
||||
},
|
||||
{
|
||||
"name": "column-move-to-column-",
|
||||
"description": "Move column to position ",
|
||||
"comment": "Requires manual remapping according to your keyboard layout, e.g. Meta+Ctrl+Shift+1 -> Meta+Ctrl+!",
|
||||
"defaultModifiers": "Meta+Ctrl+Shift",
|
||||
"fKeys": false,
|
||||
"action": "columnMoveToColumn",
|
||||
},
|
||||
{
|
||||
"name": "column-move-to-desktop-",
|
||||
"description": "Move column to desktop ",
|
||||
"defaultModifiers": "Meta+Ctrl+Shift",
|
||||
"fKeys": true,
|
||||
"action": "columnMoveToDesktop",
|
||||
},
|
||||
{
|
||||
"name": "tail-move-to-desktop-",
|
||||
"description": "Move this and all following columns to desktop ",
|
||||
"defaultModifiers": "Meta+Ctrl+Shift+Alt",
|
||||
"fKeys": true,
|
||||
"action": "tailMoveToDesktop",
|
||||
},
|
||||
];
|
||||
62
src/keyBindings/loader.ts
Normal file
62
src/keyBindings/loader.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
interface KeyBinding {
|
||||
name: string;
|
||||
description: string;
|
||||
comment?: string;
|
||||
defaultKeySequence: string;
|
||||
action: keyof ReturnType<typeof Actions.init>;
|
||||
}
|
||||
|
||||
interface NumKeyBinding {
|
||||
name: string;
|
||||
description: string;
|
||||
comment?: string;
|
||||
defaultModifiers: string;
|
||||
fKeys: boolean;
|
||||
action: keyof ReturnType<typeof Actions.initNum>;
|
||||
}
|
||||
|
||||
function catchWrap(f: () => void) {
|
||||
return () => {
|
||||
try {
|
||||
f();
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
console.log(error.stack);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function registerKeyBinding(name: string, description: string, keySequence: string, callback: () => void) {
|
||||
KWin.registerShortcut(
|
||||
"karousel-" + name,
|
||||
"Karousel: " + description,
|
||||
keySequence,
|
||||
catchWrap(callback),
|
||||
);
|
||||
}
|
||||
|
||||
function registerNumKeyBindings(name: string, description: string, modifiers: string, fKeys: boolean, callback: (i: number) => void) {
|
||||
const numPrefix = fKeys ? "F" : "";
|
||||
const n = fKeys ? 12 : 9;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const numKey = String(i + 1);
|
||||
registerKeyBinding(
|
||||
name + numKey,
|
||||
description + numKey,
|
||||
modifiers + "+" + numPrefix + numKey,
|
||||
() => callback(i),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function registerKeyBindings(world: World, config: Config) {
|
||||
const actions = Actions.init(world, config);
|
||||
for (const binding of keyBindings) {
|
||||
registerKeyBinding(binding.name, binding.description, binding.defaultKeySequence, actions[binding.action]);
|
||||
}
|
||||
|
||||
const numActions = Actions.initNum(world);
|
||||
for (const binding of numKeyBindings) {
|
||||
registerNumKeyBindings(binding.name, binding.description, binding.defaultModifiers, binding.fKeys, numActions[binding.action]);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
class Column {
|
||||
public grid: Grid;
|
||||
public gridX: number;
|
||||
public width: number; // TODO: increase column width to contain transients
|
||||
private width: number; // TODO: increase column width to contain transients
|
||||
private readonly windows: LinkedList<Window>;
|
||||
private stacked: boolean;
|
||||
private focusTaker: Window|null;
|
||||
private widthBeforeExpand: number;
|
||||
private static readonly minWidth = 10;
|
||||
|
||||
constructor(grid: Grid, prevColumn: Column|null) {
|
||||
this.gridX = 0;
|
||||
this.width = 0;
|
||||
this.windows = new LinkedList();
|
||||
this.stacked = grid.world.config.stackColumnsByDefault;
|
||||
this.stacked = grid.config.stackColumnsByDefault;
|
||||
this.focusTaker = null;
|
||||
this.widthBeforeExpand = 0;
|
||||
this.grid = grid;
|
||||
this.grid.onColumnAdded(this, prevColumn);
|
||||
}
|
||||
@@ -26,7 +25,7 @@ class Column {
|
||||
this.grid = targetGrid;
|
||||
targetGrid.onColumnAdded(this, prevColumn);
|
||||
for (const window of this.windows.iterator()) {
|
||||
window.client.kwinClient.desktop = targetGrid.desktop;
|
||||
window.client.kwinClient.desktop = targetGrid.container.desktop;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,12 +73,23 @@ class Column {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
getMinWidth() {
|
||||
let maxMinWidth = Column.minWidth;
|
||||
for (const window of this.windows.iterator()) {
|
||||
const minWidth = window.client.kwinClient.minSize.width;
|
||||
if (minWidth > maxMinWidth) {
|
||||
maxMinWidth = minWidth;
|
||||
}
|
||||
}
|
||||
return maxMinWidth;
|
||||
}
|
||||
|
||||
getMaxWidth() {
|
||||
return this.grid.tilingArea.width;
|
||||
return this.grid.container.tilingArea.width;
|
||||
}
|
||||
|
||||
setWidth(width: number, setPreferred: boolean) {
|
||||
width = Math.min(width, this.getMaxWidth());
|
||||
width = clamp(width, this.getMinWidth(), this.getMaxWidth());
|
||||
const oldWidth = this.width;
|
||||
this.width = width;
|
||||
if (setPreferred) {
|
||||
@@ -96,15 +106,14 @@ class Column {
|
||||
this.setWidth(this.width + widthDelta, setPreferred);
|
||||
}
|
||||
|
||||
expand() {
|
||||
const maxWidth = this.getMaxWidth();
|
||||
const isAlreadyExpanded = this.width === maxWidth && this.widthBeforeExpand > 0;
|
||||
if (isAlreadyExpanded) {
|
||||
this.setWidth(this.widthBeforeExpand, false);
|
||||
} else {
|
||||
this.widthBeforeExpand = this.width;
|
||||
this.setWidth(maxWidth, false);
|
||||
}
|
||||
// returns x position of left edge in grid space
|
||||
getLeft() {
|
||||
return this.gridX;
|
||||
}
|
||||
|
||||
// returns x position of right edge in grid space
|
||||
getRight() {
|
||||
return this.gridX + this.width;
|
||||
}
|
||||
|
||||
adjustWindowHeight(window: Window, heightDelta: number, top: boolean) {
|
||||
@@ -123,10 +132,10 @@ class Column {
|
||||
return;
|
||||
}
|
||||
if (nWindows === 1) {
|
||||
this.stacked = this.grid.world.config.stackColumnsByDefault;
|
||||
this.stacked = this.grid.config.stackColumnsByDefault;
|
||||
}
|
||||
|
||||
let remainingPixels = this.grid.tilingArea.height - (nWindows-1) * this.grid.world.config.gapsInnerVertical;
|
||||
let remainingPixels = this.grid.container.tilingArea.height - (nWindows-1) * this.grid.config.gapsInnerVertical;
|
||||
let remainingWindows = nWindows;
|
||||
for (const window of this.windows.iterator()) {
|
||||
const windowHeight = Math.round(remainingPixels / remainingWindows);
|
||||
@@ -157,11 +166,11 @@ class Column {
|
||||
this.arrangeStacked(x);
|
||||
return;
|
||||
}
|
||||
let y = this.grid.tilingArea.y;
|
||||
let y = this.grid.container.tilingArea.y;
|
||||
for (const window of this.windows.iterator()) {
|
||||
window.client.setShade(false);
|
||||
window.arrange(x, y, this.width, window.height);
|
||||
y += window.height + this.grid.world.config.gapsInnerVertical;
|
||||
y += window.height + this.grid.config.gapsInnerVertical;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,8 +187,8 @@ class Column {
|
||||
}
|
||||
|
||||
const nCollapsed = this.getWindowCount() - 1;
|
||||
const expandedHeight = this.grid.tilingArea.height - nCollapsed * (collapsedHeight + this.grid.world.config.gapsInnerVertical);
|
||||
let y = this.grid.tilingArea.y;
|
||||
const expandedHeight = this.grid.container.tilingArea.height - nCollapsed * (collapsedHeight + this.grid.config.gapsInnerVertical);
|
||||
let y = this.grid.container.tilingArea.y;
|
||||
for (const window of this.windows.iterator()) {
|
||||
if (window === expandedWindow) {
|
||||
window.arrange(x, y, this.width, expandedHeight);
|
||||
@@ -188,7 +197,7 @@ class Column {
|
||||
window.arrange(x, y, this.width, window.height);
|
||||
y += collapsedHeight;
|
||||
}
|
||||
y += this.grid.world.config.gapsInnerVertical;
|
||||
y += this.grid.config.gapsInnerVertical;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +208,16 @@ class Column {
|
||||
this.stacked = !this.stacked;
|
||||
}
|
||||
|
||||
public isVisible(scrollPos: ScrollPos, fullyVisible: boolean) {
|
||||
if (fullyVisible) {
|
||||
return this.getLeft() >= scrollPos.getLeft() &&
|
||||
this.getRight() <= scrollPos.getRight();
|
||||
} else {
|
||||
return this.getRight() + this.grid.config.gapsInnerHorizontal > scrollPos.getLeft() &&
|
||||
this.getLeft() - this.grid.config.gapsInnerHorizontal < scrollPos.getRight();
|
||||
}
|
||||
}
|
||||
|
||||
onWindowAdded(window: Window) {
|
||||
this.windows.insertEnd(window);
|
||||
if (this.width === 0) {
|
||||
|
||||
@@ -1,55 +1,30 @@
|
||||
class Grid {
|
||||
public readonly world: World;
|
||||
public readonly container: ScrollView;
|
||||
public readonly config: LayoutConfig;
|
||||
private readonly columns: LinkedList<Column>;
|
||||
private lastFocusedColumn: Column|null;
|
||||
private scrollX: number;
|
||||
private width: number;
|
||||
private userResize: boolean; // is any part of the grid being resized by the user
|
||||
public clientArea: QRect;
|
||||
public tilingArea: QRect;
|
||||
public readonly desktop: number;
|
||||
private readonly userResizeFinishedDelayer: Delayer;
|
||||
|
||||
constructor(world: World, desktop: number) {
|
||||
this.world = world;
|
||||
constructor(container: ScrollView, config: LayoutConfig) {
|
||||
this.container = container;
|
||||
this.config = config;
|
||||
this.columns = new LinkedList();
|
||||
this.lastFocusedColumn = null;
|
||||
this.scrollX = 0;
|
||||
this.width = 0;
|
||||
this.userResize = false;
|
||||
this.desktop = desktop;
|
||||
this.updateArea();
|
||||
this.userResizeFinishedDelayer = new Delayer(50, () => {
|
||||
// this delay prevents windows' contents from freezing after resizing
|
||||
this.autoAdjustScroll();
|
||||
this.arrange();
|
||||
this.container.onGridWidthChanged();
|
||||
this.container.arrange();
|
||||
});
|
||||
}
|
||||
|
||||
updateArea() {
|
||||
const newClientArea = workspace.clientArea(workspace.PlacementArea, 0, this.desktop);
|
||||
if (newClientArea === this.clientArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clientArea = newClientArea;
|
||||
this.tilingArea = Qt.rect(
|
||||
newClientArea.x + this.world.config.gapsOuterLeft,
|
||||
newClientArea.y + this.world.config.gapsOuterTop,
|
||||
newClientArea.width - this.world.config.gapsOuterLeft - this.world.config.gapsOuterRight,
|
||||
newClientArea.height - this.world.config.gapsOuterTop - this.world.config.gapsOuterBottom,
|
||||
)
|
||||
for (const column of this.columns.iterator()) {
|
||||
column.resizeWindows();
|
||||
}
|
||||
|
||||
this.autoAdjustScroll();
|
||||
}
|
||||
|
||||
moveColumnLeft(column: Column) {
|
||||
this.columns.moveBack(column);
|
||||
this.columnsSetX(column);
|
||||
this.autoAdjustScroll();
|
||||
this.container.onGridWidthChanged();
|
||||
}
|
||||
|
||||
moveColumnRight(column: Column) {
|
||||
@@ -60,6 +35,10 @@ class Grid {
|
||||
this.moveColumnLeft(nextColumn);
|
||||
}
|
||||
|
||||
getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
getPrevColumn(column: Column) {
|
||||
return this.columns.getPrev(column);
|
||||
}
|
||||
@@ -87,25 +66,35 @@ class Grid {
|
||||
return this.lastFocusedColumn;
|
||||
}
|
||||
|
||||
getLeftmostVisibleColumn(fullyVisible: boolean) {
|
||||
private columnsSetX(firstMovedColumn: Column|null) {
|
||||
const lastUnmovedColumn = firstMovedColumn === null ? this.columns.getLast() : this.columns.getPrev(firstMovedColumn);
|
||||
let x = lastUnmovedColumn === null ? 0 : lastUnmovedColumn.getRight() + this.config.gapsInnerHorizontal;
|
||||
if (firstMovedColumn !== null) {
|
||||
for (const column of this.columns.iteratorFrom(firstMovedColumn)) {
|
||||
column.gridX = x;
|
||||
x += column.getWidth() + this.config.gapsInnerHorizontal;
|
||||
}
|
||||
}
|
||||
this.width = x - this.config.gapsInnerHorizontal;
|
||||
}
|
||||
|
||||
getLeftmostVisibleColumn(scrollPos: ScrollPos, fullyVisible: boolean) {
|
||||
const scrollX = scrollPos.getLeft();
|
||||
for (const column of this.columns.iterator()) {
|
||||
const left = column.gridX - this.scrollX; // in screen space
|
||||
const right = left + column.width; // in screen space
|
||||
const x = fullyVisible ? left : right;
|
||||
if (x >= 0) {
|
||||
const x = fullyVisible ? column.getLeft() : column.getRight() + (this.config.gapsInnerHorizontal - 1);
|
||||
if (x >= scrollX) {
|
||||
return column;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getRightmostVisibleColumn(fullyVisible: boolean) {
|
||||
getRightmostVisibleColumn(scrollPos: ScrollPos, fullyVisible: boolean) {
|
||||
const scrollX = scrollPos.getRight();
|
||||
let last = null;
|
||||
for (const column of this.columns.iterator()) {
|
||||
const left = column.gridX - this.scrollX; // in screen space
|
||||
const right = left + column.width; // in screen space
|
||||
const x = fullyVisible ? right : left;
|
||||
if (x <= this.tilingArea.width) {
|
||||
const x = fullyVisible ? column.getRight() : column.getLeft() - (this.config.gapsInnerHorizontal - 1);
|
||||
if (x <= scrollX) {
|
||||
last = column;
|
||||
} else {
|
||||
break;
|
||||
@@ -114,75 +103,102 @@ class Grid {
|
||||
return last;
|
||||
}
|
||||
|
||||
scrollToColumn(column: Column) {
|
||||
const left = column.gridX - this.scrollX; // in screen space
|
||||
const right = left + column.width; // in screen space
|
||||
const remainingSpace = this.tilingArea.width - column.width;
|
||||
const overScrollX = Math.min(this.world.config.overscroll, Math.round(remainingSpace / 2));
|
||||
if (left < 0) {
|
||||
this.adjustScroll(left - overScrollX, false);
|
||||
} else if (right > this.tilingArea.width) {
|
||||
this.adjustScroll(right - this.tilingArea.width + overScrollX, false);
|
||||
} else {
|
||||
this.removeOverscroll();
|
||||
getVisibleColumnsWidth(scrollPos: ScrollPos, fullyVisible: boolean) {
|
||||
let width = 0;
|
||||
let nVisible = 0;
|
||||
for (const column of this.columns.iterator()) {
|
||||
if (column.isVisible(scrollPos, fullyVisible)) {
|
||||
width += column.getWidth();
|
||||
nVisible++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nVisible > 0) {
|
||||
width += (nVisible-1) * this.config.gapsInnerHorizontal;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
autoAdjustScroll() {
|
||||
const focusedWindow = this.world.getFocusedWindow();
|
||||
if (focusedWindow === null) {
|
||||
this.removeOverscroll();
|
||||
getLeftOffScreenColumn(scrollPos: ScrollPos) {
|
||||
const leftVisible = this.getLeftmostVisibleColumn(scrollPos, true);
|
||||
if (leftVisible === null) {
|
||||
return null;
|
||||
}
|
||||
return this.getPrevColumn(leftVisible);
|
||||
}
|
||||
|
||||
getRightOffScreenColumn(scrollPos: ScrollPos) {
|
||||
const rightVisible = this.getRightmostVisibleColumn(scrollPos, true);
|
||||
if (rightVisible === null) {
|
||||
return null;
|
||||
}
|
||||
return this.getNextColumn(rightVisible);
|
||||
}
|
||||
|
||||
increaseColumnWidth(column: Column) {
|
||||
const scrollPos = this.container.getScrollPosForColumn(column);
|
||||
if (this.width < scrollPos.width) {
|
||||
column.adjustWidth(scrollPos.width - this.width, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const column = focusedWindow.column;
|
||||
if (column.grid !== this) {
|
||||
let leftColumn = this.getLeftmostVisibleColumn(scrollPos, false);
|
||||
if (leftColumn === column) {
|
||||
leftColumn = null;
|
||||
}
|
||||
let rightColumn = this.getRightmostVisibleColumn(scrollPos, false);
|
||||
if (rightColumn === column) {
|
||||
rightColumn = null;
|
||||
}
|
||||
if (leftColumn === null && rightColumn === null) {
|
||||
return;
|
||||
}
|
||||
this.scrollToColumn(column);
|
||||
}
|
||||
|
||||
setScroll(x: number, force: boolean) {
|
||||
if (!force) {
|
||||
let minScroll = 0;
|
||||
let maxScroll = this.width - this.tilingArea.width;
|
||||
if (maxScroll < 0) {
|
||||
const centerScroll = Math.round(maxScroll / 2);
|
||||
minScroll = centerScroll;
|
||||
maxScroll = centerScroll;
|
||||
}
|
||||
x = clamp(x, minScroll, maxScroll);
|
||||
const leftVisibleWidth = leftColumn === null ? Infinity : leftColumn.getRight() - scrollPos.getLeft();
|
||||
const rightVisibleWidth = rightColumn === null ? Infinity : scrollPos.getRight() - rightColumn.getLeft();
|
||||
const expandLeft = leftVisibleWidth < rightVisibleWidth;
|
||||
const widthDelta = (expandLeft ? leftVisibleWidth : rightVisibleWidth) + this.config.gapsInnerHorizontal;
|
||||
if (expandLeft) {
|
||||
this.container.adjustScroll(widthDelta, false);
|
||||
}
|
||||
this.scrollX = x;
|
||||
column.adjustWidth(widthDelta, true);
|
||||
}
|
||||
|
||||
adjustScroll(dx: number, force: boolean) {
|
||||
this.setScroll(this.scrollX + dx, force);
|
||||
}
|
||||
|
||||
removeOverscroll() {
|
||||
this.setScroll(this.scrollX, false);
|
||||
}
|
||||
|
||||
columnsSetX(firstMovedColumn: Column|null) {
|
||||
const lastUnmovedColumn = firstMovedColumn === null ? this.columns.getLast() : this.columns.getPrev(firstMovedColumn);
|
||||
let x = lastUnmovedColumn === null ? 0 : lastUnmovedColumn.gridX + lastUnmovedColumn.width + this.world.config.gapsInnerHorizontal;
|
||||
if (firstMovedColumn !== null) {
|
||||
for (const column of this.columns.iteratorFrom(firstMovedColumn)) {
|
||||
column.gridX = x;
|
||||
x += column.width + this.world.config.gapsInnerHorizontal;
|
||||
}
|
||||
decreaseColumnWidth(column: Column) {
|
||||
const scrollPos = this.container.getScrollPosForColumn(column);
|
||||
if (this.width <= scrollPos.width) {
|
||||
column.setWidth(Math.round(column.getWidth() / 2), false);
|
||||
return;
|
||||
}
|
||||
this.width = x - this.world.config.gapsInnerHorizontal;
|
||||
|
||||
let leftColumn = this.getLeftOffScreenColumn(scrollPos);
|
||||
if (leftColumn === column) {
|
||||
leftColumn = null;
|
||||
}
|
||||
let rightColumn = this.getRightOffScreenColumn(scrollPos);
|
||||
if (rightColumn === column) {
|
||||
rightColumn = null;
|
||||
}
|
||||
if (leftColumn === null && rightColumn === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const leftInvisibleWidth = leftColumn === null ? Infinity : scrollPos.getLeft() - leftColumn.getLeft();
|
||||
const rightInvisibleWidth = rightColumn === null ? Infinity : rightColumn.getRight() - scrollPos.getRight();
|
||||
const shrinkLeft = leftInvisibleWidth < rightInvisibleWidth;
|
||||
const widthDelta = (shrinkLeft ? leftInvisibleWidth : rightInvisibleWidth);
|
||||
if (shrinkLeft) {
|
||||
const maxDelta = column.getWidth() - column.getMinWidth();
|
||||
this.container.adjustScroll(-Math.min(widthDelta, maxDelta), false);
|
||||
}
|
||||
column.adjustWidth(-widthDelta, true);
|
||||
}
|
||||
|
||||
arrange() {
|
||||
// TODO (optimization): only arrange visible windows
|
||||
this.updateArea();
|
||||
let x = this.tilingArea.x - this.scrollX;
|
||||
arrange(x: number) {
|
||||
for (const column of this.columns.iterator()) {
|
||||
column.arrange(x);
|
||||
x += column.getWidth() + this.world.config.gapsInnerHorizontal;
|
||||
x += column.getWidth() + this.config.gapsInnerHorizontal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,25 +209,24 @@ class Grid {
|
||||
this.columns.insertAfter(column, prevColumn);
|
||||
}
|
||||
this.columnsSetX(column);
|
||||
this.autoAdjustScroll();
|
||||
this.container.onGridWidthChanged();
|
||||
}
|
||||
|
||||
onColumnRemoved(column: Column, passFocus: boolean) {
|
||||
const isLastColumn = this.columns.length() === 1;
|
||||
const nextColumn = this.getNextColumn(column);
|
||||
const columnToFocus = isLastColumn ? null : this.getPrevColumn(column) ?? nextColumn;
|
||||
if (column === this.lastFocusedColumn) {
|
||||
this.lastFocusedColumn = null;
|
||||
this.lastFocusedColumn = columnToFocus;
|
||||
}
|
||||
|
||||
const lastColumn = this.columns.length() === 1;
|
||||
const columnToFocus = lastColumn || !passFocus ? null : this.getPrevColumn(column) ?? this.getNextColumn(column);
|
||||
const nextColumn = this.columns.getNext(column);
|
||||
|
||||
this.columns.remove(column);
|
||||
|
||||
this.columnsSetX(nextColumn);
|
||||
if (columnToFocus !== null) {
|
||||
|
||||
if (passFocus && columnToFocus !== null) {
|
||||
columnToFocus.focus();
|
||||
} else {
|
||||
this.removeOverscroll();
|
||||
this.container.onGridWidthChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,14 +235,14 @@ class Grid {
|
||||
const firstMovedColumn = movedLeft ? column : this.getNextColumn(column);
|
||||
this.columns.move(column, prevColumn);
|
||||
this.columnsSetX(firstMovedColumn);
|
||||
this.autoAdjustScroll();
|
||||
this.container.onGridReordered();
|
||||
}
|
||||
|
||||
onColumnWidthChanged(column: Column, oldWidth: number, width: number) {
|
||||
const nextColumn = this.columns.getNext(column);
|
||||
this.columnsSetX(nextColumn);
|
||||
if (!this.userResize) {
|
||||
this.autoAdjustScroll();
|
||||
this.container.onGridWidthChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +252,13 @@ class Grid {
|
||||
lastFocusedColumn.restoreToTiled();
|
||||
}
|
||||
this.lastFocusedColumn = column;
|
||||
this.scrollToColumn(column);
|
||||
this.container.scrollToColumn(column);
|
||||
}
|
||||
|
||||
onScreenSizeChanged() {
|
||||
for (const column of this.columns.iterator()) {
|
||||
column.resizeWindows();
|
||||
}
|
||||
}
|
||||
|
||||
onUserResizeStarted() {
|
||||
|
||||
6
src/layout/LayoutConfig.ts
Normal file
6
src/layout/LayoutConfig.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
type LayoutConfig = {
|
||||
gapsInnerHorizontal: number,
|
||||
gapsInnerVertical: number,
|
||||
stackColumnsByDefault: boolean,
|
||||
resizeNeighborColumn: boolean,
|
||||
}
|
||||
17
src/layout/ScrollPos.ts
Normal file
17
src/layout/ScrollPos.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
class ScrollPos {
|
||||
public readonly x: number;
|
||||
public readonly width: number;
|
||||
|
||||
constructor(x: number, width: number) {
|
||||
this.x = x;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public getLeft() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public getRight() {
|
||||
return this.x + this.width;
|
||||
}
|
||||
}
|
||||
154
src/layout/ScrollView.ts
Normal file
154
src/layout/ScrollView.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
class ScrollView {
|
||||
public readonly world: World;
|
||||
public readonly grid: Grid;
|
||||
public readonly desktop: number;
|
||||
private readonly config: ScrollView.Config;
|
||||
private scrollX: number;
|
||||
public clientArea: QRect;
|
||||
public tilingArea: QRect;
|
||||
|
||||
constructor(world: World, desktop: number, config: ScrollView.Config, layoutConfig: LayoutConfig) {
|
||||
this.config = config;
|
||||
this.world = world;
|
||||
this.scrollX = 0;
|
||||
this.desktop = desktop;
|
||||
this.grid = new Grid(this, layoutConfig);
|
||||
this.updateArea();
|
||||
}
|
||||
|
||||
updateArea() {
|
||||
const newClientArea = workspace.clientArea(workspace.PlacementArea, 0, this.desktop);
|
||||
if (newClientArea === this.clientArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clientArea = newClientArea;
|
||||
this.tilingArea = Qt.rect(
|
||||
newClientArea.x + this.config.marginLeft,
|
||||
newClientArea.y + this.config.marginTop,
|
||||
newClientArea.width - this.config.marginLeft - this.config.marginRight,
|
||||
newClientArea.height - this.config.marginTop - this.config.marginBottom,
|
||||
)
|
||||
this.grid.onScreenSizeChanged();
|
||||
|
||||
this.autoAdjustScroll();
|
||||
}
|
||||
|
||||
// calculates ScrollPos that scrolls the column into view
|
||||
public getScrollPosForColumn(column: Column) {
|
||||
const left = column.getLeft();
|
||||
const right = column.getRight();
|
||||
const initialScrollPos = this.getCurrentScrollPos();
|
||||
|
||||
let targetScrollX: number;
|
||||
if (left < initialScrollPos.getLeft()) {
|
||||
targetScrollX = this.clampScrollX(left);
|
||||
} else if (right > initialScrollPos.getRight()) {
|
||||
targetScrollX = this.clampScrollX(right - this.tilingArea.width);
|
||||
} else {
|
||||
return this.getScrollPos(this.clampScrollX(this.scrollX));
|
||||
}
|
||||
|
||||
const overscroll = this.getTargetOverscroll(targetScrollX, left < initialScrollPos.getLeft());
|
||||
return this.getScrollPos(this.clampScrollX(targetScrollX + overscroll));
|
||||
}
|
||||
|
||||
private getTargetOverscroll(targetScrollX: number, scrollLeft: boolean) {
|
||||
if (this.config.overscroll === 0) {
|
||||
return 0;
|
||||
}
|
||||
const visibleColumnsWidth = this.grid.getVisibleColumnsWidth(this.getScrollPos(targetScrollX), true);
|
||||
const remainingSpace = this.tilingArea.width - visibleColumnsWidth;
|
||||
const overscrollX = Math.min(this.config.overscroll, Math.round(remainingSpace / 2));
|
||||
const direction = scrollLeft ? -1 : 1;
|
||||
return overscrollX * direction;
|
||||
}
|
||||
|
||||
scrollToColumn(column: Column) {
|
||||
this.scrollX = this.getScrollPosForColumn(column).x;
|
||||
}
|
||||
|
||||
scrollCenterColumn(column: Column) {
|
||||
const windowCenter = column.getLeft() + column.getWidth() / 2;
|
||||
const screenCenter = this.scrollX + this.tilingArea.width / 2;
|
||||
this.adjustScroll(Math.round(windowCenter - screenCenter), false);
|
||||
}
|
||||
|
||||
private autoAdjustScroll() {
|
||||
const focusedWindow = this.world.getFocusedWindow();
|
||||
if (focusedWindow === null) {
|
||||
this.removeOverscroll();
|
||||
return;
|
||||
}
|
||||
|
||||
const column = focusedWindow.column;
|
||||
if (column.grid !== this.grid) {
|
||||
return;
|
||||
}
|
||||
this.scrollToColumn(column);
|
||||
}
|
||||
|
||||
private getScrollPos(scrollX: number) {
|
||||
return new ScrollPos(scrollX, this.tilingArea.width);
|
||||
}
|
||||
|
||||
public getCurrentScrollPos() {
|
||||
return this.getScrollPos(this.scrollX);
|
||||
}
|
||||
|
||||
private clampScrollX(x: number) {
|
||||
let minScroll = 0;
|
||||
let maxScroll = this.grid.getWidth() - this.tilingArea.width;
|
||||
if (maxScroll < 0) {
|
||||
const centerScroll = Math.round(maxScroll / 2);
|
||||
minScroll = centerScroll;
|
||||
maxScroll = centerScroll;
|
||||
}
|
||||
return clamp(x, minScroll, maxScroll);
|
||||
}
|
||||
|
||||
private setScroll(x: number, force: boolean) {
|
||||
this.scrollX = force ? x : this.clampScrollX(x);
|
||||
}
|
||||
|
||||
private applyScrollPos(scrollPos: ScrollPos) {
|
||||
this.scrollX = scrollPos.x;
|
||||
}
|
||||
|
||||
adjustScroll(dx: number, force: boolean) {
|
||||
this.setScroll(this.scrollX + dx, force);
|
||||
}
|
||||
|
||||
private removeOverscroll() {
|
||||
this.setScroll(this.scrollX, false);
|
||||
}
|
||||
|
||||
public arrange() {
|
||||
// TODO (optimization): only arrange visible windows
|
||||
this.updateArea();
|
||||
this.grid.arrange(this.tilingArea.x - this.scrollX);
|
||||
this.world.ensureFocusedTransientsVisible(); // TODO: refactor - call from elsewhere
|
||||
}
|
||||
|
||||
public onGridWidthChanged() {
|
||||
this.autoAdjustScroll();
|
||||
}
|
||||
|
||||
public onGridReordered() {
|
||||
this.autoAdjustScroll();
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.grid.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
module ScrollView {
|
||||
export type Config = {
|
||||
marginTop: number,
|
||||
marginBottom: number,
|
||||
marginLeft: number,
|
||||
marginRight: number,
|
||||
overscroll: number,
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class Window {
|
||||
|
||||
arrange(x: number, y: number, width: number, height: number) {
|
||||
if (this.skipArrange) {
|
||||
// window is being manually resized, prevent fighting with the user
|
||||
// window is maximized, fullscreen, or being manually resized, prevent fighting with the user
|
||||
return;
|
||||
}
|
||||
this.client.place(x, y, width, height);
|
||||
@@ -83,15 +83,25 @@ class Window {
|
||||
}
|
||||
}
|
||||
|
||||
onUserResize(oldGeometry: QRect) {
|
||||
onUserResize(oldGeometry: QRect, resizeNeighborColumn: boolean) {
|
||||
const newGeometry = this.client.kwinClient.frameGeometry;
|
||||
const widthDelta = newGeometry.width - oldGeometry.width;
|
||||
const heightDelta = newGeometry.height - oldGeometry.height;
|
||||
if (widthDelta !== 0) {
|
||||
this.column.adjustWidth(widthDelta, true);
|
||||
if (newGeometry.x !== oldGeometry.x) {
|
||||
this.column.grid.adjustScroll(widthDelta, true);
|
||||
let leftEdgeDelta = newGeometry.left - oldGeometry.left;
|
||||
const resizingLeftSide = leftEdgeDelta !== 0;
|
||||
if (resizeNeighborColumn && this.column.grid.config.resizeNeighborColumn) {
|
||||
const neighborColumn = resizingLeftSide ? this.column.grid.getPrevColumn(this.column) : this.column.grid.getNextColumn(this.column);
|
||||
if (neighborColumn !== null) {
|
||||
const oldNeighborWidth = neighborColumn.getWidth();
|
||||
neighborColumn.adjustWidth(-widthDelta, true);
|
||||
if (resizingLeftSide) {
|
||||
leftEdgeDelta -= neighborColumn.getWidth() - oldNeighborWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.column.grid.container.adjustScroll(-leftEdgeDelta, true);
|
||||
}
|
||||
if (heightDelta !== 0) {
|
||||
this.column.adjustWindowHeight(this, heightDelta, newGeometry.y !== oldGeometry.y);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
function init() {
|
||||
const config = loadConfig();
|
||||
const world = new World(config);
|
||||
registerShortcuts(world);
|
||||
registerKeyBindings(world, config);
|
||||
return world;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class WindowRuleEnforcer {
|
||||
}
|
||||
|
||||
shouldTile(kwinClient: AbstractClient) {
|
||||
return canTileNow(kwinClient) && (
|
||||
return Clients.canTileNow(kwinClient) && (
|
||||
this.preferTiling.matches(kwinClient) ||
|
||||
kwinClient.normalWindow && kwinClient.managed && !this.preferFloating.matches(kwinClient)
|
||||
);
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
function catchWrap(f: () => void) {
|
||||
return () => {
|
||||
try {
|
||||
f();
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
console.log(error.stack);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function registerShortcutDbg(title: string, text: string, keySequence: string, callback: () => void) {
|
||||
KWin.registerShortcut(title, text, keySequence, catchWrap(callback));
|
||||
}
|
||||
|
||||
function registerNumShortcuts(title: string, text: string, keySequence: string, callback: (i: number) => void, n: number) {
|
||||
for (let i = 0; i < n; i++) {
|
||||
const numKey = String(i + 1);
|
||||
registerShortcutDbg(title+numKey, text+numKey, keySequence+numKey, () => callback(i));
|
||||
}
|
||||
}
|
||||
|
||||
function registerShortcuts(world: World) {
|
||||
const actions = initActions(world);
|
||||
|
||||
registerShortcutDbg("karousel-window-toggle-floating", "Karousel: Toggle floating", "Meta+Space", actions.windowToggleFloating);
|
||||
|
||||
registerShortcutDbg("karousel-focus-left", "Karousel: Move focus left", "Meta+A", actions.focusLeft);
|
||||
registerShortcutDbg("karousel-focus-right", "Karousel: Move focus right", "Meta+D", actions.focusRight);
|
||||
registerShortcutDbg("karousel-focus-up", "Karousel: Move focus up", "Meta+W", actions.focusUp);
|
||||
registerShortcutDbg("karousel-focus-down", "Karousel: Move focus down", "Meta+S", actions.focusDown);
|
||||
registerShortcutDbg("karousel-focus-start", "Karousel: Move focus to start", "Meta+Home", actions.focusStart);
|
||||
registerShortcutDbg("karousel-focus-end", "Karousel: Move focus to end", "Meta+End", actions.focusEnd);
|
||||
|
||||
registerShortcutDbg("karousel-window-move-left", "Karousel: Move window left", "Meta+Shift+A", actions.windowMoveLeft);
|
||||
registerShortcutDbg("karousel-window-move-right", "Karousel: Move window right", "Meta+Shift+D", actions.windowMoveRight);
|
||||
registerShortcutDbg("karousel-window-move-up", "Karousel: Move window up", "Meta+Shift+W", actions.windowMoveUp);
|
||||
registerShortcutDbg("karousel-window-move-down", "Karousel: Move window down", "Meta+Shift+S", actions.windowMoveDown);
|
||||
registerShortcutDbg("karousel-window-move-start", "Karousel: Move window to start", "Meta+Shift+Home", actions.windowMoveStart);
|
||||
registerShortcutDbg("karousel-window-move-end", "Karousel: Move window to end", "Meta+Shift+End", actions.windowMoveEnd);
|
||||
registerShortcutDbg("karousel-window-expand", "Karousel: Expand window", "Meta+X", actions.windowExpand);
|
||||
|
||||
registerShortcutDbg("karousel-column-move-left", "Karousel: Move column left", "Meta+Ctrl+Shift+A", actions.columnMoveLeft);
|
||||
registerShortcutDbg("karousel-column-move-right", "Karousel: Move column right", "Meta+Ctrl+Shift+D", actions.columnMoveRight);
|
||||
registerShortcutDbg("karousel-column-move-start", "Karousel: Move column to start", "Meta+Ctrl+Shift+Home", actions.columnMoveStart);
|
||||
registerShortcutDbg("karousel-column-move-end", "Karousel: Move column to end", "Meta+Ctrl+Shift+End", actions.columnMoveEnd);
|
||||
registerShortcutDbg("karousel-column-expand", "Karousel: Expand column", "Meta+Ctrl+X", actions.columnExpand);
|
||||
|
||||
registerShortcutDbg("karousel-grid-scroll-focused", "Karousel: Scroll to focused window", "Meta+Alt+Return", actions.gridScrollFocused);
|
||||
registerShortcutDbg("karousel-grid-scroll-left-column", "Karousel: Scroll one column to the left", "Meta+Alt+A", actions.gridScrollLeftColumn);
|
||||
registerShortcutDbg("karousel-grid-scroll-left-column", "Karousel: Scroll one column to the left", "Meta+Alt+A", actions.gridScrollLeftColumn);
|
||||
registerShortcutDbg("karousel-grid-scroll-right-column", "Karousel: Scroll one column to the right", "Meta+Alt+D", actions.gridScrollRightColumn);
|
||||
registerShortcutDbg("karousel-grid-scroll-left", "Karousel: Scroll left", "Meta+Alt+PgUp", actions.gridScrollLeft);
|
||||
registerShortcutDbg("karousel-grid-scroll-right", "Karousel: Scroll right", "Meta+Alt+PgDown", actions.gridScrollRight);
|
||||
registerShortcutDbg("karousel-grid-scroll-start", "Karousel: Scroll to start", "Meta+Alt+Home", actions.gridScrollStart);
|
||||
registerShortcutDbg("karousel-grid-scroll-end", "Karousel: Scroll to end", "Meta+Alt+End", actions.gridScrollEnd);
|
||||
|
||||
registerNumShortcuts("karousel-focus-", "Karousel: Move focus to column ", "Meta+", actions.focusColumn, 9);
|
||||
registerNumShortcuts("karousel-window-move-to-column-", "Karousel: Move window to column ", "Meta+Shift+", actions.windowMoveToColumn, 9);
|
||||
registerNumShortcuts("karousel-column-move-to-column-", "Karousel: Move column to position ", "Meta+Ctrl+Shift+", actions.columnMoveToColumn, 9);
|
||||
registerNumShortcuts("karousel-column-move-to-desktop-", "Karousel: Move column to desktop ", "Meta+Ctrl+Shift+F", actions.columnMoveToDesktop, 12);
|
||||
registerNumShortcuts("karousel-tail-move-to-desktop-", "Karousel: Move this and all following columns to desktop ", "Meta+Ctrl+Shift+Alt+F", actions.tailMoveToDesktop, 12);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ function initWorkspaceSignalHandlers(world: World) {
|
||||
|
||||
manager.connect(workspace.clientAdded, (kwinClient: AbstractClient) => {
|
||||
console.assert(!world.hasClient(kwinClient));
|
||||
if (canTileEver(kwinClient)) {
|
||||
if (Clients.canTileEver(kwinClient)) {
|
||||
// never open new tileable clients on all desktops or activities
|
||||
if (kwinClient.desktop <= 0) {
|
||||
kwinClient.desktop = workspace.currentDesktop;
|
||||
@@ -31,7 +31,7 @@ function initWorkspaceSignalHandlers(world: World) {
|
||||
manager.connect(workspace.clientMaximizeSet, (kwinClient: AbstractClient, horizontally: boolean, vertically: boolean) => {
|
||||
world.doIfTiled(kwinClient, false, (window, column, grid) => {
|
||||
window.onMaximizedChanged(horizontally, vertically);
|
||||
grid.arrange();
|
||||
grid.container.arrange();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,14 +42,14 @@ function initWorkspaceSignalHandlers(world: World) {
|
||||
world.onClientFocused(kwinClient);
|
||||
world.doIfTiled(kwinClient, true, (window, column, grid) => {
|
||||
window.onFocused();
|
||||
grid.arrange();
|
||||
grid.container.arrange();
|
||||
});
|
||||
});
|
||||
|
||||
manager.connect(workspace.clientFullScreenSet, (kwinClient: X11Client, fullScreen: boolean, user: boolean) => {
|
||||
world.doIfTiled(kwinClient, false, (window, column, grid) => {
|
||||
window.onFullScreenChanged(fullScreen);
|
||||
grid.arrange();
|
||||
grid.container.arrange();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class ClientStateTiled {
|
||||
const grid = world.getClientGrid(client.kwinClient);
|
||||
const column = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
|
||||
const window = new Window(client, column);
|
||||
grid.arrange();
|
||||
grid.container.arrange();
|
||||
|
||||
this.window = window;
|
||||
this.signalManager = ClientStateTiled.initSignalManager(world, window);
|
||||
@@ -21,9 +21,9 @@ class ClientStateTiled {
|
||||
const grid = window.column.grid;
|
||||
const clientWrapper = window.client;
|
||||
window.destroy(passFocus);
|
||||
grid.arrange();
|
||||
grid.container.arrange();
|
||||
|
||||
clientWrapper.prepareForFloating(grid.clientArea);
|
||||
clientWrapper.prepareForFloating(grid.container.clientArea);
|
||||
}
|
||||
|
||||
static initSignalManager(world: World, window: Window) {
|
||||
@@ -51,7 +51,7 @@ class ClientStateTiled {
|
||||
|
||||
let lastResize = false;
|
||||
manager.connect(kwinClient.moveResizedChanged, () => {
|
||||
if (kwinClient.move) {
|
||||
if (world.untileOnDrag && kwinClient.move) {
|
||||
world.untileClient(kwinClient);
|
||||
return;
|
||||
}
|
||||
@@ -67,17 +67,24 @@ class ClientStateTiled {
|
||||
lastResize = resize;
|
||||
});
|
||||
|
||||
let cursorChangedAfterResizeStart = false;
|
||||
manager.connect(kwinClient.moveResizeCursorChanged, () => {
|
||||
cursorChangedAfterResizeStart = true;
|
||||
});
|
||||
manager.connect(kwinClient.clientStartUserMovedResized, () => {
|
||||
cursorChangedAfterResizeStart = false;
|
||||
});
|
||||
|
||||
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: TopLevel, oldGeometry: QRect) => {
|
||||
console.assert(!kwinClient.move, "moved clients are removed in kwinClient.moveResizedChanged");
|
||||
const grid = window.column.grid;
|
||||
const scrollView = window.column.grid.container;
|
||||
if (kwinClient.resize) {
|
||||
window.onUserResize(oldGeometry);
|
||||
grid.arrange();
|
||||
window.onUserResize(oldGeometry, !cursorChangedAfterResizeStart);
|
||||
scrollView.arrange();
|
||||
} else {
|
||||
const maximized = rectEqual(kwinClient.frameGeometry, grid.clientArea);
|
||||
const maximized = rectEqual(kwinClient.frameGeometry, scrollView.clientArea);
|
||||
if (!client.isManipulatingGeometry() && !kwinClient.fullScreen && !maximized) {
|
||||
window.onProgrammaticResize(oldGeometry);
|
||||
grid.arrange();
|
||||
scrollView.arrange();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -97,7 +104,7 @@ class ClientStateTiled {
|
||||
|
||||
const newColumn = new Column(newGrid, newGrid.getLastFocusedColumn() ?? newGrid.getLastColumn());
|
||||
window.moveToColumn(newColumn);
|
||||
oldGrid.arrange();
|
||||
newGrid.arrange();
|
||||
oldGrid.container.arrange();
|
||||
newGrid.container.arrange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,25 @@ class ClientWrapper {
|
||||
this.transients.splice(i, 1);
|
||||
}
|
||||
|
||||
public ensureTransientsVisible(screenSize: QRect) {
|
||||
for (const transient of this.transients) {
|
||||
if (transient.stateManager.getState() instanceof ClientStateFloating) {
|
||||
transient.ensureVisible(screenSize);
|
||||
transient.ensureTransientsVisible(screenSize);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ensureVisible(screenSize: QRect) {
|
||||
const frame = this.kwinClient.frameGeometry;
|
||||
if (frame.left < 0) {
|
||||
frame.x = 0;
|
||||
} else if (frame.right > screenSize.width) {
|
||||
frame.x = screenSize.width - frame.width;
|
||||
}
|
||||
}
|
||||
|
||||
destroy(passFocus: boolean) {
|
||||
this.stateManager.destroy(passFocus);
|
||||
this.signalManager.destroy();
|
||||
|
||||
21
src/world/Clients.ts
Normal file
21
src/world/Clients.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
module Clients {
|
||||
export function canTileEver(kwinClient: AbstractClient) {
|
||||
return kwinClient.resizeable;
|
||||
}
|
||||
|
||||
export function canTileNow(kwinClient: AbstractClient) {
|
||||
return canTileEver(kwinClient) && !kwinClient.minimized && kwinClient.desktop > 0 && kwinClient.activities.length === 1;
|
||||
}
|
||||
|
||||
export function makeTileable(kwinClient: AbstractClient) {
|
||||
if (kwinClient.minimized) {
|
||||
kwinClient.minimized = false;
|
||||
}
|
||||
if (kwinClient.desktop <= 0) {
|
||||
kwinClient.desktop = workspace.currentDesktop;
|
||||
}
|
||||
if (kwinClient.activities.length !== 1) {
|
||||
kwinClient.activities = [workspace.currentActivity];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
class GridManager {
|
||||
private readonly world: World;
|
||||
private readonly gridsPerActivity: Map<string, Grid[]>;
|
||||
private nDesktops: number;
|
||||
|
||||
constructor(world: World, currentActivity: string, nDesktops: number) {
|
||||
this.world = world;
|
||||
this.gridsPerActivity = new Map();
|
||||
this.nDesktops = 0;
|
||||
this.setNDesktops(nDesktops);
|
||||
this.addActivity(currentActivity);
|
||||
}
|
||||
|
||||
get(activity: string, desktopNumber: number) {
|
||||
const desktopIndex = desktopNumber - 1;
|
||||
if (desktopIndex >= this.nDesktops || this.nDesktops < 0) {
|
||||
throw new Error("invalid desktop number: " + String(desktopNumber));
|
||||
}
|
||||
if (!this.gridsPerActivity.has(activity)) {
|
||||
this.addActivity(activity);
|
||||
}
|
||||
return this.gridsPerActivity.get(activity)![desktopIndex];
|
||||
}
|
||||
|
||||
setNDesktops(nDesktops: number) {
|
||||
if (nDesktops > this.nDesktops) {
|
||||
this.addDesktopsToActivities(nDesktops - this.nDesktops);
|
||||
} else if (nDesktops < this.nDesktops) {
|
||||
this.removeDesktopsFromActivities(this.nDesktops - nDesktops);
|
||||
}
|
||||
this.nDesktops = nDesktops;
|
||||
}
|
||||
|
||||
private addDesktopsToActivities(n: number) {
|
||||
for (const grids of this.gridsPerActivity.values()) {
|
||||
this.addDesktops(grids, n);
|
||||
}
|
||||
}
|
||||
|
||||
private addDesktops(grids: Grid[], n: number) {
|
||||
const nStart = grids.length;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const desktopNumber = nStart + i + 1;
|
||||
grids.push(new Grid(this.world, desktopNumber));
|
||||
}
|
||||
}
|
||||
|
||||
private removeDesktopsFromActivities(n: number) {
|
||||
const lastRemainingDesktopIndex = this.nDesktops - n - 1;
|
||||
for (const grids of this.gridsPerActivity.values()) {
|
||||
const targetGrid = grids[lastRemainingDesktopIndex];
|
||||
for (let i = 0; i < n; i++) {
|
||||
const removedGrid = grids.pop()!;
|
||||
removedGrid.evacuate(targetGrid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addActivity(activity: string) {
|
||||
const grids: Grid[] = [];
|
||||
this.addDesktops(grids, this.nDesktops);
|
||||
this.gridsPerActivity.set(activity, grids);
|
||||
}
|
||||
|
||||
removeActivity(activity: string) {
|
||||
const removedGrids = this.gridsPerActivity.get(activity)!;
|
||||
this.gridsPerActivity.delete(activity);
|
||||
const targetActivityGrids = this.gridsPerActivity.values().next().value;
|
||||
for (let i = 0; i < removedGrids.length; i++) {
|
||||
removedGrids[i].evacuate(targetActivityGrids[i]);
|
||||
}
|
||||
}
|
||||
|
||||
*grids() {
|
||||
for (const grids of this.gridsPerActivity.values()) {
|
||||
for (const grid of grids) {
|
||||
yield grid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/world/ScrollViewManager.ts
Normal file
85
src/world/ScrollViewManager.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
class ScrollViewManager {
|
||||
private readonly world: World;
|
||||
private readonly config: ScrollView.Config;
|
||||
public readonly layoutConfig: LayoutConfig;
|
||||
private readonly scrollViewsPerActivity: Map<string, ScrollView[]>;
|
||||
private nDesktops: number;
|
||||
|
||||
constructor(world: World, config: ScrollView.Config, layoutConfig: LayoutConfig, currentActivity: string, nDesktops: number) {
|
||||
this.config = config;
|
||||
this.layoutConfig = layoutConfig;
|
||||
this.world = world;
|
||||
this.scrollViewsPerActivity = new Map();
|
||||
this.nDesktops = 0;
|
||||
this.setNDesktops(nDesktops);
|
||||
this.addActivity(currentActivity);
|
||||
}
|
||||
|
||||
get(activity: string, desktopNumber: number) {
|
||||
const desktopIndex = desktopNumber - 1;
|
||||
if (desktopIndex >= this.nDesktops || this.nDesktops < 0) {
|
||||
throw new Error("invalid desktop number: " + String(desktopNumber));
|
||||
}
|
||||
if (!this.scrollViewsPerActivity.has(activity)) {
|
||||
this.addActivity(activity);
|
||||
}
|
||||
return this.scrollViewsPerActivity.get(activity)![desktopIndex];
|
||||
}
|
||||
|
||||
setNDesktops(nDesktops: number) {
|
||||
if (nDesktops > this.nDesktops) {
|
||||
this.addDesktopsToActivities(nDesktops - this.nDesktops);
|
||||
} else if (nDesktops < this.nDesktops) {
|
||||
this.removeDesktopsFromActivities(this.nDesktops - nDesktops);
|
||||
}
|
||||
this.nDesktops = nDesktops;
|
||||
}
|
||||
|
||||
private addDesktopsToActivities(n: number) {
|
||||
for (const scrollViews of this.scrollViewsPerActivity.values()) {
|
||||
this.addDesktops(scrollViews, n);
|
||||
}
|
||||
}
|
||||
|
||||
private addDesktops(scrollViews: ScrollView[], n: number) {
|
||||
const nStart = scrollViews.length;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const desktopNumber = nStart + i + 1;
|
||||
scrollViews.push(new ScrollView(this.world, desktopNumber, this.config, this.layoutConfig));
|
||||
}
|
||||
}
|
||||
|
||||
private removeDesktopsFromActivities(n: number) {
|
||||
const lastRemainingDesktopIndex = this.nDesktops - n - 1;
|
||||
for (const scrollViews of this.scrollViewsPerActivity.values()) {
|
||||
const targetScrollView = scrollViews[lastRemainingDesktopIndex];
|
||||
for (let i = 0; i < n; i++) {
|
||||
const removedScrollView = scrollViews.pop()!;
|
||||
removedScrollView.grid.evacuate(targetScrollView.grid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addActivity(activity: string) {
|
||||
const scrollViews: ScrollView[] = [];
|
||||
this.addDesktops(scrollViews, this.nDesktops);
|
||||
this.scrollViewsPerActivity.set(activity, scrollViews);
|
||||
}
|
||||
|
||||
removeActivity(activity: string) {
|
||||
const removedScrollViews = this.scrollViewsPerActivity.get(activity)!;
|
||||
this.scrollViewsPerActivity.delete(activity);
|
||||
const targetActivityScrollViews = this.scrollViewsPerActivity.values().next().value;
|
||||
for (let i = 0; i < removedScrollViews.length; i++) {
|
||||
removedScrollViews[i].grid.evacuate(targetActivityScrollViews[i]);
|
||||
}
|
||||
}
|
||||
|
||||
*scrollViews() {
|
||||
for (const scrollViews of this.scrollViewsPerActivity.values()) {
|
||||
for (const scrollView of scrollViews) {
|
||||
yield scrollView;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
class World {
|
||||
public readonly config: Config;
|
||||
private readonly gridManager: GridManager;
|
||||
public readonly untileOnDrag: boolean;
|
||||
private readonly scrollViewManager: ScrollViewManager;
|
||||
private readonly clientMap: Map<AbstractClient, ClientWrapper>;
|
||||
private lastFocusedClient: AbstractClient|null;
|
||||
private readonly workspaceSignalManager: SignalManager;
|
||||
@@ -8,7 +8,7 @@ class World {
|
||||
private readonly screenResizedDelayer: Delayer;
|
||||
|
||||
constructor(config: Config) {
|
||||
this.config = config;
|
||||
this.untileOnDrag = config.untileOnDrag;
|
||||
this.clientMap = new Map();
|
||||
this.lastFocusedClient = null;
|
||||
this.workspaceSignalManager = initWorkspaceSignalHandlers(this);
|
||||
@@ -23,18 +23,30 @@ class World {
|
||||
|
||||
this.screenResizedDelayer = new Delayer(1000, () => {
|
||||
// this delay ensures that docks get taken into account by `workspace.clientArea`
|
||||
const gridManager = this.gridManager; // workaround for bug in Qt5's JS engine
|
||||
for (const grid of gridManager.grids()) {
|
||||
grid.arrange();
|
||||
const gridManager = this.scrollViewManager; // workaround for bug in Qt5's JS engine
|
||||
for (const scrollView of gridManager.scrollViews()) {
|
||||
scrollView.arrange();
|
||||
}
|
||||
});
|
||||
|
||||
this.gridManager = new GridManager(this, workspace.currentActivity, workspace.desktops);
|
||||
this.scrollViewManager = new ScrollViewManager(
|
||||
this,
|
||||
{
|
||||
marginTop: config.gapsOuterTop,
|
||||
marginBottom: config.gapsOuterBottom,
|
||||
marginLeft: config.gapsOuterLeft,
|
||||
marginRight: config.gapsOuterRight,
|
||||
overscroll: config.overscroll,
|
||||
},
|
||||
config,
|
||||
workspace.currentActivity,
|
||||
workspace.desktops,
|
||||
);
|
||||
this.addExistingClients();
|
||||
}
|
||||
|
||||
updateDesktops() {
|
||||
this.gridManager.setNDesktops(workspace.desktops);
|
||||
this.scrollViewManager.setNDesktops(workspace.desktops);
|
||||
}
|
||||
|
||||
private addExistingClients() {
|
||||
@@ -47,7 +59,7 @@ class World {
|
||||
|
||||
getGrid(activity: string, desktopNumber: number) {
|
||||
console.assert(desktopNumber > 0 && desktopNumber <= workspace.desktops);
|
||||
return this.gridManager.get(activity, desktopNumber);
|
||||
return this.scrollViewManager.get(activity, desktopNumber).grid;
|
||||
}
|
||||
|
||||
getGridInCurrentActivity(desktopNumber: number) {
|
||||
@@ -101,6 +113,12 @@ class World {
|
||||
return transientFor;
|
||||
}
|
||||
|
||||
public ensureFocusedTransientsVisible() {
|
||||
this.doIfTiledFocused(true, (window, column, grid) => {
|
||||
window.client.ensureTransientsVisible(grid.container.clientArea);
|
||||
});
|
||||
}
|
||||
|
||||
minimizeClient(kwinClient: AbstractClient) {
|
||||
const client = this.clientMap.get(kwinClient);
|
||||
if (client === undefined) {
|
||||
@@ -149,8 +167,8 @@ class World {
|
||||
}
|
||||
|
||||
const clientState = client.stateManager.getState();
|
||||
if (clientState instanceof ClientStateFloating && canTileEver(kwinClient)) {
|
||||
makeTileable(kwinClient);
|
||||
if (clientState instanceof ClientStateFloating && Clients.canTileEver(kwinClient)) {
|
||||
Clients.makeTileable(kwinClient);
|
||||
client.stateManager.setState(new ClientStateTiled(this, client), false);
|
||||
} else if (clientState instanceof ClientStateTiled) {
|
||||
client.stateManager.setState(new ClientStateFloating(), false);
|
||||
@@ -215,8 +233,8 @@ class World {
|
||||
destroy() {
|
||||
this.workspaceSignalManager.destroy();
|
||||
this.removeAllClients();
|
||||
for (const grid of this.gridManager.grids()) {
|
||||
grid.destroy();
|
||||
for (const scrollView of this.scrollViewManager.scrollViews()) {
|
||||
scrollView.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user