Compare commits
40 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 |
11
README.md
11
README.md
@@ -22,6 +22,8 @@ Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM) an
|
|||||||
- Doesn't support windows on multiple activities
|
- Doesn't support windows on multiple activities
|
||||||
|
|
||||||
## Key bindings
|
## 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 |
|
| Shortcut | Action |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Meta+Space | Toggle floating |
|
| Meta+Space | Toggle floating |
|
||||||
@@ -37,19 +39,16 @@ Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM) an
|
|||||||
| Meta+Shift+S | Move window down |
|
| Meta+Shift+S | Move window down |
|
||||||
| Meta+Shift+Home | Move window to start |
|
| Meta+Shift+Home | Move window to start |
|
||||||
| Meta+Shift+End | Move window to end |
|
| Meta+Shift+End | Move window to end |
|
||||||
| Meta+X | Expand window (Expands focused window vertically; toggles stacked layout for focused column) |
|
| Meta+X | Toggle stacked layout for focused column |
|
||||||
| Meta+Ctrl+Shift+A | Move column left |
|
| Meta+Ctrl+Shift+A | Move column left |
|
||||||
| Meta+Ctrl+Shift+D | Move column right |
|
| Meta+Ctrl+Shift+D | Move column right |
|
||||||
| Meta+Ctrl+Shift+Home | Move column to start |
|
| Meta+Ctrl+Shift+Home | Move column to start |
|
||||||
| Meta+Ctrl+Shift+End | Move column to end |
|
| Meta+Ctrl+Shift+End | Move column to end |
|
||||||
| Meta+Ctrl+X | Expand column (Expands focused column horizontally to fill the screen) |
|
| Meta+Ctrl++ | Increase column width |
|
||||||
| Meta+Alt++ | Expand fully visible columns (Expands fully visible columns to fill the screen) |
|
| Meta+Ctrl+- | Decrease column width |
|
||||||
| Meta+Alt+- | Shrink visible columns (Shrinks fully and partially visible columns, making them fully visible and filling the screen) |
|
|
||||||
| Meta+Alt+Return | Center focused window (Scrolls so that the focused window is centered in the screen) |
|
| 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+A | Scroll one column to the left |
|
||||||
| Meta+Alt+D | Scroll one column to the right |
|
| Meta+Alt+D | Scroll one column to the right |
|
||||||
| Meta+Alt+PgUp | Scroll left |
|
|
||||||
| Meta+Alt+PgDown | Scroll right |
|
|
||||||
| Meta+Alt+Home | Scroll to start |
|
| Meta+Alt+Home | Scroll to start |
|
||||||
| Meta+Alt+End | Scroll to end |
|
| Meta+Alt+End | Scroll to end |
|
||||||
| Meta+[N] | Move focus to column N |
|
| Meta+[N] | Move focus to column N |
|
||||||
|
|||||||
@@ -209,6 +209,14 @@
|
|||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item row="8" column="1">
|
<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">
|
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Stack columns by default</string>
|
<string>Stack columns by default</string>
|
||||||
@@ -216,7 +224,15 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<spacer name="bottomSpacer_tab_general">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
}],
|
}],
|
||||||
"Id": "karousel",
|
"Id": "karousel",
|
||||||
"ServiceTypes": ["KWin/Script"],
|
"ServiceTypes": ["KWin/Script"],
|
||||||
"Version": "0.2.1",
|
"Version": "0.3.1",
|
||||||
"License": "GPLv3",
|
"License": "GPLv3",
|
||||||
"Website": "https://github.com/peterfajdiga/karousel",
|
"Website": "https://github.com/peterfajdiga/karousel",
|
||||||
"BugReportUrl": "https://github.com/peterfajdiga/karousel/issues"
|
"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,
|
||||||
|
}
|
||||||
|
}
|
||||||
354
src/actions.ts
354
src/actions.ts
@@ -1,354 +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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
expandVisibleColumns: () => {
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
grid.rescaleVisibleColumns(true, true);
|
|
||||||
grid.arrange();
|
|
||||||
},
|
|
||||||
|
|
||||||
shrinkVisibleColumns: () => {
|
|
||||||
const grid = world.getCurrentGrid();
|
|
||||||
grid.rescaleVisibleColumns(false, false);
|
|
||||||
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.scrollCenterColumn(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();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function initNumActions(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.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,
|
gapsInnerVertical: number,
|
||||||
overscroll: number,
|
overscroll: number,
|
||||||
manualScrollStep: number,
|
manualScrollStep: number,
|
||||||
|
untileOnDrag: boolean,
|
||||||
stackColumnsByDefault: boolean,
|
stackColumnsByDefault: boolean,
|
||||||
|
resizeNeighborColumn: boolean,
|
||||||
windowRules: string,
|
windowRules: string,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,11 +88,21 @@ const configDef = [
|
|||||||
"type": "UInt",
|
"type": "UInt",
|
||||||
"default": 200
|
"default": 200
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "untileOnDrag",
|
||||||
|
"type": "Bool",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "stackColumnsByDefault",
|
"name": "stackColumnsByDefault",
|
||||||
"type": "Bool",
|
"type": "Bool",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "resizeNeighborColumn",
|
||||||
|
"type": "Bool",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "windowRules",
|
"name": "windowRules",
|
||||||
"type": "String",
|
"type": "String",
|
||||||
|
|||||||
@@ -80,11 +80,10 @@ const keyBindings: KeyBinding[] = [
|
|||||||
"action": "windowMoveEnd",
|
"action": "windowMoveEnd",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "window-expand",
|
"name": "column-toggle-stacked",
|
||||||
"description": "Expand window",
|
"description": "Toggle stacked layout for focused column",
|
||||||
"comment": "Expands focused window vertically; toggles stacked layout for focused column",
|
|
||||||
"defaultKeySequence": "Meta+X",
|
"defaultKeySequence": "Meta+X",
|
||||||
"action": "windowExpand",
|
"action": "columnToggleStacked",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "column-move-left",
|
"name": "column-move-left",
|
||||||
@@ -111,25 +110,16 @@ const keyBindings: KeyBinding[] = [
|
|||||||
"action": "columnMoveEnd",
|
"action": "columnMoveEnd",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "column-expand",
|
"name": "column-width-increase",
|
||||||
"description": "Expand column",
|
"description": "Increase column width",
|
||||||
"comment": "Expands focused column horizontally to fill the screen",
|
"defaultKeySequence": "Meta+Ctrl++",
|
||||||
"defaultKeySequence": "Meta+Ctrl+X",
|
"action": "columnWidthIncrease",
|
||||||
"action": "columnExpand",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "expand-visible-columns",
|
"name": "column-width-decrease",
|
||||||
"description": "Expand fully visible columns",
|
"description": "Decrease column width",
|
||||||
"comment": "Expands fully visible columns to fill the screen",
|
"defaultKeySequence": "Meta+Ctrl+-",
|
||||||
"defaultKeySequence": "Meta+Alt++",
|
"action": "columnWidthDecrease",
|
||||||
"action": "expandVisibleColumns",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shrink-visible-columns",
|
|
||||||
"description": "Shrink visible columns",
|
|
||||||
"comment": "Shrinks fully and partially visible columns, making them fully visible and filling the screen",
|
|
||||||
"defaultKeySequence": "Meta+Alt+-",
|
|
||||||
"action": "shrinkVisibleColumns",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "grid-scroll-focused",
|
"name": "grid-scroll-focused",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ interface KeyBinding {
|
|||||||
description: string;
|
description: string;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
defaultKeySequence: string;
|
defaultKeySequence: string;
|
||||||
action: keyof ReturnType<typeof initActions>;
|
action: keyof ReturnType<typeof Actions.init>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NumKeyBinding {
|
interface NumKeyBinding {
|
||||||
@@ -12,7 +12,7 @@ interface NumKeyBinding {
|
|||||||
comment?: string;
|
comment?: string;
|
||||||
defaultModifiers: string;
|
defaultModifiers: string;
|
||||||
fKeys: boolean;
|
fKeys: boolean;
|
||||||
action: keyof ReturnType<typeof initNumActions>;
|
action: keyof ReturnType<typeof Actions.initNum>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function catchWrap(f: () => void) {
|
function catchWrap(f: () => void) {
|
||||||
@@ -49,13 +49,13 @@ function registerNumKeyBindings(name: string, description: string, modifiers: st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerKeyBindings(world: World) {
|
function registerKeyBindings(world: World, config: Config) {
|
||||||
const actions = initActions(world);
|
const actions = Actions.init(world, config);
|
||||||
for (const binding of keyBindings) {
|
for (const binding of keyBindings) {
|
||||||
registerKeyBinding(binding.name, binding.description, binding.defaultKeySequence, actions[binding.action]);
|
registerKeyBinding(binding.name, binding.description, binding.defaultKeySequence, actions[binding.action]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const numActions = initNumActions(world);
|
const numActions = Actions.initNum(world);
|
||||||
for (const binding of numKeyBindings) {
|
for (const binding of numKeyBindings) {
|
||||||
registerNumKeyBindings(binding.name, binding.description, binding.defaultModifiers, binding.fKeys, numActions[binding.action]);
|
registerNumKeyBindings(binding.name, binding.description, binding.defaultModifiers, binding.fKeys, numActions[binding.action]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
class Column {
|
class Column {
|
||||||
public grid: Grid;
|
public grid: Grid;
|
||||||
public gridX: number;
|
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 readonly windows: LinkedList<Window>;
|
||||||
private stacked: boolean;
|
private stacked: boolean;
|
||||||
private focusTaker: Window|null;
|
private focusTaker: Window|null;
|
||||||
private widthBeforeExpand: number;
|
private static readonly minWidth = 10;
|
||||||
|
|
||||||
constructor(grid: Grid, prevColumn: Column|null) {
|
constructor(grid: Grid, prevColumn: Column|null) {
|
||||||
this.gridX = 0;
|
this.gridX = 0;
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.windows = new LinkedList();
|
this.windows = new LinkedList();
|
||||||
this.stacked = grid.world.config.stackColumnsByDefault;
|
this.stacked = grid.config.stackColumnsByDefault;
|
||||||
this.focusTaker = null;
|
this.focusTaker = null;
|
||||||
this.widthBeforeExpand = 0;
|
|
||||||
this.grid = grid;
|
this.grid = grid;
|
||||||
this.grid.onColumnAdded(this, prevColumn);
|
this.grid.onColumnAdded(this, prevColumn);
|
||||||
}
|
}
|
||||||
@@ -26,7 +25,7 @@ class Column {
|
|||||||
this.grid = targetGrid;
|
this.grid = targetGrid;
|
||||||
targetGrid.onColumnAdded(this, prevColumn);
|
targetGrid.onColumnAdded(this, prevColumn);
|
||||||
for (const window of this.windows.iterator()) {
|
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;
|
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() {
|
getMaxWidth() {
|
||||||
return this.grid.tilingArea.width;
|
return this.grid.container.tilingArea.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWidth(width: number, setPreferred: boolean) {
|
setWidth(width: number, setPreferred: boolean) {
|
||||||
width = Math.min(width, this.getMaxWidth());
|
width = clamp(width, this.getMinWidth(), this.getMaxWidth());
|
||||||
const oldWidth = this.width;
|
const oldWidth = this.width;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
if (setPreferred) {
|
if (setPreferred) {
|
||||||
@@ -96,15 +106,14 @@ class Column {
|
|||||||
this.setWidth(this.width + widthDelta, setPreferred);
|
this.setWidth(this.width + widthDelta, setPreferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
expand() {
|
// returns x position of left edge in grid space
|
||||||
const maxWidth = this.getMaxWidth();
|
getLeft() {
|
||||||
const isAlreadyExpanded = this.width === maxWidth && this.widthBeforeExpand > 0;
|
return this.gridX;
|
||||||
if (isAlreadyExpanded) {
|
}
|
||||||
this.setWidth(this.widthBeforeExpand, false);
|
|
||||||
} else {
|
// returns x position of right edge in grid space
|
||||||
this.widthBeforeExpand = this.width;
|
getRight() {
|
||||||
this.setWidth(maxWidth, false);
|
return this.gridX + this.width;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustWindowHeight(window: Window, heightDelta: number, top: boolean) {
|
adjustWindowHeight(window: Window, heightDelta: number, top: boolean) {
|
||||||
@@ -123,10 +132,10 @@ class Column {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (nWindows === 1) {
|
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;
|
let remainingWindows = nWindows;
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
const windowHeight = Math.round(remainingPixels / remainingWindows);
|
const windowHeight = Math.round(remainingPixels / remainingWindows);
|
||||||
@@ -157,11 +166,11 @@ class Column {
|
|||||||
this.arrangeStacked(x);
|
this.arrangeStacked(x);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let y = this.grid.tilingArea.y;
|
let y = this.grid.container.tilingArea.y;
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
window.client.setShade(false);
|
window.client.setShade(false);
|
||||||
window.arrange(x, y, this.width, window.height);
|
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 nCollapsed = this.getWindowCount() - 1;
|
||||||
const expandedHeight = this.grid.tilingArea.height - nCollapsed * (collapsedHeight + this.grid.world.config.gapsInnerVertical);
|
const expandedHeight = this.grid.container.tilingArea.height - nCollapsed * (collapsedHeight + this.grid.config.gapsInnerVertical);
|
||||||
let y = this.grid.tilingArea.y;
|
let y = this.grid.container.tilingArea.y;
|
||||||
for (const window of this.windows.iterator()) {
|
for (const window of this.windows.iterator()) {
|
||||||
if (window === expandedWindow) {
|
if (window === expandedWindow) {
|
||||||
window.arrange(x, y, this.width, expandedHeight);
|
window.arrange(x, y, this.width, expandedHeight);
|
||||||
@@ -188,7 +197,7 @@ class Column {
|
|||||||
window.arrange(x, y, this.width, window.height);
|
window.arrange(x, y, this.width, window.height);
|
||||||
y += collapsedHeight;
|
y += collapsedHeight;
|
||||||
}
|
}
|
||||||
y += this.grid.world.config.gapsInnerVertical;
|
y += this.grid.config.gapsInnerVertical;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +208,16 @@ class Column {
|
|||||||
this.stacked = !this.stacked;
|
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) {
|
onWindowAdded(window: Window) {
|
||||||
this.windows.insertEnd(window);
|
this.windows.insertEnd(window);
|
||||||
if (this.width === 0) {
|
if (this.width === 0) {
|
||||||
|
|||||||
@@ -1,55 +1,30 @@
|
|||||||
class Grid {
|
class Grid {
|
||||||
public readonly world: World;
|
public readonly container: ScrollView;
|
||||||
|
public readonly config: LayoutConfig;
|
||||||
private readonly columns: LinkedList<Column>;
|
private readonly columns: LinkedList<Column>;
|
||||||
private lastFocusedColumn: Column|null;
|
private lastFocusedColumn: Column|null;
|
||||||
private scrollX: number;
|
|
||||||
private width: number;
|
private width: number;
|
||||||
private userResize: boolean; // is any part of the grid being resized by the user
|
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;
|
private readonly userResizeFinishedDelayer: Delayer;
|
||||||
|
|
||||||
constructor(world: World, desktop: number) {
|
constructor(container: ScrollView, config: LayoutConfig) {
|
||||||
this.world = world;
|
this.container = container;
|
||||||
|
this.config = config;
|
||||||
this.columns = new LinkedList();
|
this.columns = new LinkedList();
|
||||||
this.lastFocusedColumn = null;
|
this.lastFocusedColumn = null;
|
||||||
this.scrollX = 0;
|
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.userResize = false;
|
this.userResize = false;
|
||||||
this.desktop = desktop;
|
|
||||||
this.updateArea();
|
|
||||||
this.userResizeFinishedDelayer = new Delayer(50, () => {
|
this.userResizeFinishedDelayer = new Delayer(50, () => {
|
||||||
// this delay prevents windows' contents from freezing after resizing
|
// this delay prevents windows' contents from freezing after resizing
|
||||||
this.autoAdjustScroll();
|
this.container.onGridWidthChanged();
|
||||||
this.arrange();
|
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) {
|
moveColumnLeft(column: Column) {
|
||||||
this.columns.moveBack(column);
|
this.columns.moveBack(column);
|
||||||
this.columnsSetX(column);
|
this.columnsSetX(column);
|
||||||
this.autoAdjustScroll();
|
this.container.onGridWidthChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
moveColumnRight(column: Column) {
|
moveColumnRight(column: Column) {
|
||||||
@@ -60,6 +35,10 @@ class Grid {
|
|||||||
this.moveColumnLeft(nextColumn);
|
this.moveColumnLeft(nextColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWidth() {
|
||||||
|
return this.width;
|
||||||
|
}
|
||||||
|
|
||||||
getPrevColumn(column: Column) {
|
getPrevColumn(column: Column) {
|
||||||
return this.columns.getPrev(column);
|
return this.columns.getPrev(column);
|
||||||
}
|
}
|
||||||
@@ -87,25 +66,35 @@ class Grid {
|
|||||||
return this.lastFocusedColumn;
|
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()) {
|
for (const column of this.columns.iterator()) {
|
||||||
const left = column.gridX - this.scrollX; // in screen space
|
const x = fullyVisible ? column.getLeft() : column.getRight() + (this.config.gapsInnerHorizontal - 1);
|
||||||
const right = left + column.width; // in screen space
|
if (x >= scrollX) {
|
||||||
const x = fullyVisible ? left : right;
|
|
||||||
if (x >= 0) {
|
|
||||||
return column;
|
return column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRightmostVisibleColumn(fullyVisible: boolean) {
|
getRightmostVisibleColumn(scrollPos: ScrollPos, fullyVisible: boolean) {
|
||||||
|
const scrollX = scrollPos.getRight();
|
||||||
let last = null;
|
let last = null;
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
const left = column.gridX - this.scrollX; // in screen space
|
const x = fullyVisible ? column.getRight() : column.getLeft() - (this.config.gapsInnerHorizontal - 1);
|
||||||
const right = left + column.width; // in screen space
|
if (x <= scrollX) {
|
||||||
const x = fullyVisible ? right : left;
|
|
||||||
if (x <= this.tilingArea.width) {
|
|
||||||
last = column;
|
last = column;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
@@ -114,111 +103,102 @@ class Grid {
|
|||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
rescaleVisibleColumns(fullyVisible: boolean, allowScaleUp: boolean) {
|
getVisibleColumnsWidth(scrollPos: ScrollPos, fullyVisible: boolean) {
|
||||||
const startColumn = this.getLeftmostVisibleColumn(fullyVisible);
|
let width = 0;
|
||||||
const endColumn = this.getRightmostVisibleColumn(fullyVisible);
|
let nVisible = 0;
|
||||||
if (startColumn === null || endColumn === null) {
|
for (const column of this.columns.iterator()) {
|
||||||
return;
|
if (column.isVisible(scrollPos, fullyVisible)) {
|
||||||
}
|
width += column.getWidth();
|
||||||
|
nVisible++;
|
||||||
const startX = startColumn.gridX;
|
|
||||||
const endX = endColumn.gridX + endColumn.width;
|
|
||||||
const width = endX - startX;
|
|
||||||
let remainingWidth = this.tilingArea.width - 2 * this.world.config.overscroll;
|
|
||||||
const scaleRatio = remainingWidth / width;
|
|
||||||
if (!allowScaleUp && scaleRatio >= 1.0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const column of this.columns.iteratorFrom(startColumn)) {
|
|
||||||
if (column !== endColumn) {
|
|
||||||
const newWidth = Math.round(column.width * scaleRatio);
|
|
||||||
column.setWidth(newWidth, true);
|
|
||||||
remainingWidth -= newWidth + this.world.config.gapsInnerHorizontal;
|
|
||||||
} else {
|
|
||||||
column.setWidth(remainingWidth, true);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setScroll(startX - this.world.config.overscroll, false);
|
if (nVisible > 0) {
|
||||||
}
|
width += (nVisible-1) * this.config.gapsInnerHorizontal;
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollCenterColumn(column: Column) {
|
getLeftOffScreenColumn(scrollPos: ScrollPos) {
|
||||||
const windowCenter = column.gridX + column.width / 2 + this.world.config.gapsInnerHorizontal - this.scrollX; // in screen space
|
const leftVisible = this.getLeftmostVisibleColumn(scrollPos, true);
|
||||||
const screenCenter = this.tilingArea.x + this.tilingArea.width / 2;
|
if (leftVisible === null) {
|
||||||
this.adjustScroll(Math.round(windowCenter - screenCenter), false);
|
return null;
|
||||||
|
}
|
||||||
|
return this.getPrevColumn(leftVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
autoAdjustScroll() {
|
getRightOffScreenColumn(scrollPos: ScrollPos) {
|
||||||
const focusedWindow = this.world.getFocusedWindow();
|
const rightVisible = this.getRightmostVisibleColumn(scrollPos, true);
|
||||||
if (focusedWindow === null) {
|
if (rightVisible === null) {
|
||||||
this.removeOverscroll();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const column = focusedWindow.column;
|
let leftColumn = this.getLeftmostVisibleColumn(scrollPos, false);
|
||||||
if (column.grid !== this) {
|
if (leftColumn === column) {
|
||||||
|
leftColumn = null;
|
||||||
|
}
|
||||||
|
let rightColumn = this.getRightmostVisibleColumn(scrollPos, false);
|
||||||
|
if (rightColumn === column) {
|
||||||
|
rightColumn = null;
|
||||||
|
}
|
||||||
|
if (leftColumn === null && rightColumn === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.scrollToColumn(column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setScroll(x: number, force: boolean) {
|
const leftVisibleWidth = leftColumn === null ? Infinity : leftColumn.getRight() - scrollPos.getLeft();
|
||||||
if (!force) {
|
const rightVisibleWidth = rightColumn === null ? Infinity : scrollPos.getRight() - rightColumn.getLeft();
|
||||||
let minScroll = 0;
|
const expandLeft = leftVisibleWidth < rightVisibleWidth;
|
||||||
let maxScroll = this.width - this.tilingArea.width;
|
const widthDelta = (expandLeft ? leftVisibleWidth : rightVisibleWidth) + this.config.gapsInnerHorizontal;
|
||||||
if (maxScroll < 0) {
|
if (expandLeft) {
|
||||||
const centerScroll = Math.round(maxScroll / 2);
|
this.container.adjustScroll(widthDelta, false);
|
||||||
minScroll = centerScroll;
|
|
||||||
maxScroll = centerScroll;
|
|
||||||
}
|
|
||||||
x = clamp(x, minScroll, maxScroll);
|
|
||||||
}
|
}
|
||||||
this.scrollX = x;
|
column.adjustWidth(widthDelta, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustScroll(dx: number, force: boolean) {
|
decreaseColumnWidth(column: Column) {
|
||||||
this.setScroll(this.scrollX + dx, force);
|
const scrollPos = this.container.getScrollPosForColumn(column);
|
||||||
}
|
if (this.width <= scrollPos.width) {
|
||||||
|
column.setWidth(Math.round(column.getWidth() / 2), false);
|
||||||
private removeOverscroll() {
|
return;
|
||||||
this.setScroll(this.scrollX, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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() {
|
arrange(x: number) {
|
||||||
// TODO (optimization): only arrange visible windows
|
|
||||||
this.updateArea();
|
|
||||||
let x = this.tilingArea.x - this.scrollX;
|
|
||||||
for (const column of this.columns.iterator()) {
|
for (const column of this.columns.iterator()) {
|
||||||
column.arrange(x);
|
column.arrange(x);
|
||||||
x += column.getWidth() + this.world.config.gapsInnerHorizontal;
|
x += column.getWidth() + this.config.gapsInnerHorizontal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +209,7 @@ class Grid {
|
|||||||
this.columns.insertAfter(column, prevColumn);
|
this.columns.insertAfter(column, prevColumn);
|
||||||
}
|
}
|
||||||
this.columnsSetX(column);
|
this.columnsSetX(column);
|
||||||
this.autoAdjustScroll();
|
this.container.onGridWidthChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onColumnRemoved(column: Column, passFocus: boolean) {
|
onColumnRemoved(column: Column, passFocus: boolean) {
|
||||||
@@ -246,7 +226,7 @@ class Grid {
|
|||||||
if (passFocus && columnToFocus !== null) {
|
if (passFocus && columnToFocus !== null) {
|
||||||
columnToFocus.focus();
|
columnToFocus.focus();
|
||||||
} else {
|
} else {
|
||||||
this.removeOverscroll();
|
this.container.onGridWidthChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,14 +235,14 @@ class Grid {
|
|||||||
const firstMovedColumn = movedLeft ? column : this.getNextColumn(column);
|
const firstMovedColumn = movedLeft ? column : this.getNextColumn(column);
|
||||||
this.columns.move(column, prevColumn);
|
this.columns.move(column, prevColumn);
|
||||||
this.columnsSetX(firstMovedColumn);
|
this.columnsSetX(firstMovedColumn);
|
||||||
this.autoAdjustScroll();
|
this.container.onGridReordered();
|
||||||
}
|
}
|
||||||
|
|
||||||
onColumnWidthChanged(column: Column, oldWidth: number, width: number) {
|
onColumnWidthChanged(column: Column, oldWidth: number, width: number) {
|
||||||
const nextColumn = this.columns.getNext(column);
|
const nextColumn = this.columns.getNext(column);
|
||||||
this.columnsSetX(nextColumn);
|
this.columnsSetX(nextColumn);
|
||||||
if (!this.userResize) {
|
if (!this.userResize) {
|
||||||
this.autoAdjustScroll();
|
this.container.onGridWidthChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +252,13 @@ class Grid {
|
|||||||
lastFocusedColumn.restoreToTiled();
|
lastFocusedColumn.restoreToTiled();
|
||||||
}
|
}
|
||||||
this.lastFocusedColumn = column;
|
this.lastFocusedColumn = column;
|
||||||
this.scrollToColumn(column);
|
this.container.scrollToColumn(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
onScreenSizeChanged() {
|
||||||
|
for (const column of this.columns.iterator()) {
|
||||||
|
column.resizeWindows();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserResizeStarted() {
|
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) {
|
arrange(x: number, y: number, width: number, height: number) {
|
||||||
if (this.skipArrange) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
this.client.place(x, y, width, height);
|
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 newGeometry = this.client.kwinClient.frameGeometry;
|
||||||
const widthDelta = newGeometry.width - oldGeometry.width;
|
const widthDelta = newGeometry.width - oldGeometry.width;
|
||||||
const heightDelta = newGeometry.height - oldGeometry.height;
|
const heightDelta = newGeometry.height - oldGeometry.height;
|
||||||
if (widthDelta !== 0) {
|
if (widthDelta !== 0) {
|
||||||
this.column.adjustWidth(widthDelta, true);
|
this.column.adjustWidth(widthDelta, true);
|
||||||
if (newGeometry.x !== oldGeometry.x) {
|
let leftEdgeDelta = newGeometry.left - oldGeometry.left;
|
||||||
this.column.grid.adjustScroll(widthDelta, true);
|
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) {
|
if (heightDelta !== 0) {
|
||||||
this.column.adjustWindowHeight(this, heightDelta, newGeometry.y !== oldGeometry.y);
|
this.column.adjustWindowHeight(this, heightDelta, newGeometry.y !== oldGeometry.y);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
function init() {
|
function init() {
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const world = new World(config);
|
const world = new World(config);
|
||||||
registerKeyBindings(world);
|
registerKeyBindings(world, config);
|
||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class WindowRuleEnforcer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldTile(kwinClient: AbstractClient) {
|
shouldTile(kwinClient: AbstractClient) {
|
||||||
return canTileNow(kwinClient) && (
|
return Clients.canTileNow(kwinClient) && (
|
||||||
this.preferTiling.matches(kwinClient) ||
|
this.preferTiling.matches(kwinClient) ||
|
||||||
kwinClient.normalWindow && kwinClient.managed && !this.preferFloating.matches(kwinClient)
|
kwinClient.normalWindow && kwinClient.managed && !this.preferFloating.matches(kwinClient)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ function initWorkspaceSignalHandlers(world: World) {
|
|||||||
|
|
||||||
manager.connect(workspace.clientAdded, (kwinClient: AbstractClient) => {
|
manager.connect(workspace.clientAdded, (kwinClient: AbstractClient) => {
|
||||||
console.assert(!world.hasClient(kwinClient));
|
console.assert(!world.hasClient(kwinClient));
|
||||||
if (canTileEver(kwinClient)) {
|
if (Clients.canTileEver(kwinClient)) {
|
||||||
// never open new tileable clients on all desktops or activities
|
// never open new tileable clients on all desktops or activities
|
||||||
if (kwinClient.desktop <= 0) {
|
if (kwinClient.desktop <= 0) {
|
||||||
kwinClient.desktop = workspace.currentDesktop;
|
kwinClient.desktop = workspace.currentDesktop;
|
||||||
@@ -31,7 +31,7 @@ function initWorkspaceSignalHandlers(world: World) {
|
|||||||
manager.connect(workspace.clientMaximizeSet, (kwinClient: AbstractClient, horizontally: boolean, vertically: boolean) => {
|
manager.connect(workspace.clientMaximizeSet, (kwinClient: AbstractClient, horizontally: boolean, vertically: boolean) => {
|
||||||
world.doIfTiled(kwinClient, false, (window, column, grid) => {
|
world.doIfTiled(kwinClient, false, (window, column, grid) => {
|
||||||
window.onMaximizedChanged(horizontally, vertically);
|
window.onMaximizedChanged(horizontally, vertically);
|
||||||
grid.arrange();
|
grid.container.arrange();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,14 +42,14 @@ function initWorkspaceSignalHandlers(world: World) {
|
|||||||
world.onClientFocused(kwinClient);
|
world.onClientFocused(kwinClient);
|
||||||
world.doIfTiled(kwinClient, true, (window, column, grid) => {
|
world.doIfTiled(kwinClient, true, (window, column, grid) => {
|
||||||
window.onFocused();
|
window.onFocused();
|
||||||
grid.arrange();
|
grid.container.arrange();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
manager.connect(workspace.clientFullScreenSet, (kwinClient: X11Client, fullScreen: boolean, user: boolean) => {
|
manager.connect(workspace.clientFullScreenSet, (kwinClient: X11Client, fullScreen: boolean, user: boolean) => {
|
||||||
world.doIfTiled(kwinClient, false, (window, column, grid) => {
|
world.doIfTiled(kwinClient, false, (window, column, grid) => {
|
||||||
window.onFullScreenChanged(fullScreen);
|
window.onFullScreenChanged(fullScreen);
|
||||||
grid.arrange();
|
grid.container.arrange();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class ClientStateTiled {
|
|||||||
const grid = world.getClientGrid(client.kwinClient);
|
const grid = world.getClientGrid(client.kwinClient);
|
||||||
const column = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
|
const column = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
|
||||||
const window = new Window(client, column);
|
const window = new Window(client, column);
|
||||||
grid.arrange();
|
grid.container.arrange();
|
||||||
|
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.signalManager = ClientStateTiled.initSignalManager(world, window);
|
this.signalManager = ClientStateTiled.initSignalManager(world, window);
|
||||||
@@ -21,9 +21,9 @@ class ClientStateTiled {
|
|||||||
const grid = window.column.grid;
|
const grid = window.column.grid;
|
||||||
const clientWrapper = window.client;
|
const clientWrapper = window.client;
|
||||||
window.destroy(passFocus);
|
window.destroy(passFocus);
|
||||||
grid.arrange();
|
grid.container.arrange();
|
||||||
|
|
||||||
clientWrapper.prepareForFloating(grid.clientArea);
|
clientWrapper.prepareForFloating(grid.container.clientArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
static initSignalManager(world: World, window: Window) {
|
static initSignalManager(world: World, window: Window) {
|
||||||
@@ -51,7 +51,7 @@ class ClientStateTiled {
|
|||||||
|
|
||||||
let lastResize = false;
|
let lastResize = false;
|
||||||
manager.connect(kwinClient.moveResizedChanged, () => {
|
manager.connect(kwinClient.moveResizedChanged, () => {
|
||||||
if (kwinClient.move) {
|
if (world.untileOnDrag && kwinClient.move) {
|
||||||
world.untileClient(kwinClient);
|
world.untileClient(kwinClient);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -67,17 +67,24 @@ class ClientStateTiled {
|
|||||||
lastResize = resize;
|
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) => {
|
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: TopLevel, oldGeometry: QRect) => {
|
||||||
console.assert(!kwinClient.move, "moved clients are removed in kwinClient.moveResizedChanged");
|
const scrollView = window.column.grid.container;
|
||||||
const grid = window.column.grid;
|
|
||||||
if (kwinClient.resize) {
|
if (kwinClient.resize) {
|
||||||
window.onUserResize(oldGeometry);
|
window.onUserResize(oldGeometry, !cursorChangedAfterResizeStart);
|
||||||
grid.arrange();
|
scrollView.arrange();
|
||||||
} else {
|
} else {
|
||||||
const maximized = rectEqual(kwinClient.frameGeometry, grid.clientArea);
|
const maximized = rectEqual(kwinClient.frameGeometry, scrollView.clientArea);
|
||||||
if (!client.isManipulatingGeometry() && !kwinClient.fullScreen && !maximized) {
|
if (!client.isManipulatingGeometry() && !kwinClient.fullScreen && !maximized) {
|
||||||
window.onProgrammaticResize(oldGeometry);
|
window.onProgrammaticResize(oldGeometry);
|
||||||
grid.arrange();
|
scrollView.arrange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -97,7 +104,7 @@ class ClientStateTiled {
|
|||||||
|
|
||||||
const newColumn = new Column(newGrid, newGrid.getLastFocusedColumn() ?? newGrid.getLastColumn());
|
const newColumn = new Column(newGrid, newGrid.getLastFocusedColumn() ?? newGrid.getLastColumn());
|
||||||
window.moveToColumn(newColumn);
|
window.moveToColumn(newColumn);
|
||||||
oldGrid.arrange();
|
oldGrid.container.arrange();
|
||||||
newGrid.arrange();
|
newGrid.container.arrange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,25 @@ class ClientWrapper {
|
|||||||
this.transients.splice(i, 1);
|
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) {
|
destroy(passFocus: boolean) {
|
||||||
this.stateManager.destroy(passFocus);
|
this.stateManager.destroy(passFocus);
|
||||||
this.signalManager.destroy();
|
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 {
|
class World {
|
||||||
public readonly config: Config;
|
public readonly untileOnDrag: boolean;
|
||||||
private readonly gridManager: GridManager;
|
private readonly scrollViewManager: ScrollViewManager;
|
||||||
private readonly clientMap: Map<AbstractClient, ClientWrapper>;
|
private readonly clientMap: Map<AbstractClient, ClientWrapper>;
|
||||||
private lastFocusedClient: AbstractClient|null;
|
private lastFocusedClient: AbstractClient|null;
|
||||||
private readonly workspaceSignalManager: SignalManager;
|
private readonly workspaceSignalManager: SignalManager;
|
||||||
@@ -8,7 +8,7 @@ class World {
|
|||||||
private readonly screenResizedDelayer: Delayer;
|
private readonly screenResizedDelayer: Delayer;
|
||||||
|
|
||||||
constructor(config: Config) {
|
constructor(config: Config) {
|
||||||
this.config = config;
|
this.untileOnDrag = config.untileOnDrag;
|
||||||
this.clientMap = new Map();
|
this.clientMap = new Map();
|
||||||
this.lastFocusedClient = null;
|
this.lastFocusedClient = null;
|
||||||
this.workspaceSignalManager = initWorkspaceSignalHandlers(this);
|
this.workspaceSignalManager = initWorkspaceSignalHandlers(this);
|
||||||
@@ -23,18 +23,30 @@ class World {
|
|||||||
|
|
||||||
this.screenResizedDelayer = new Delayer(1000, () => {
|
this.screenResizedDelayer = new Delayer(1000, () => {
|
||||||
// this delay ensures that docks get taken into account by `workspace.clientArea`
|
// this delay ensures that docks get taken into account by `workspace.clientArea`
|
||||||
const gridManager = this.gridManager; // workaround for bug in Qt5's JS engine
|
const gridManager = this.scrollViewManager; // workaround for bug in Qt5's JS engine
|
||||||
for (const grid of gridManager.grids()) {
|
for (const scrollView of gridManager.scrollViews()) {
|
||||||
grid.arrange();
|
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();
|
this.addExistingClients();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDesktops() {
|
updateDesktops() {
|
||||||
this.gridManager.setNDesktops(workspace.desktops);
|
this.scrollViewManager.setNDesktops(workspace.desktops);
|
||||||
}
|
}
|
||||||
|
|
||||||
private addExistingClients() {
|
private addExistingClients() {
|
||||||
@@ -47,7 +59,7 @@ class World {
|
|||||||
|
|
||||||
getGrid(activity: string, desktopNumber: number) {
|
getGrid(activity: string, desktopNumber: number) {
|
||||||
console.assert(desktopNumber > 0 && desktopNumber <= workspace.desktops);
|
console.assert(desktopNumber > 0 && desktopNumber <= workspace.desktops);
|
||||||
return this.gridManager.get(activity, desktopNumber);
|
return this.scrollViewManager.get(activity, desktopNumber).grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
getGridInCurrentActivity(desktopNumber: number) {
|
getGridInCurrentActivity(desktopNumber: number) {
|
||||||
@@ -101,6 +113,12 @@ class World {
|
|||||||
return transientFor;
|
return transientFor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ensureFocusedTransientsVisible() {
|
||||||
|
this.doIfTiledFocused(true, (window, column, grid) => {
|
||||||
|
window.client.ensureTransientsVisible(grid.container.clientArea);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
minimizeClient(kwinClient: AbstractClient) {
|
minimizeClient(kwinClient: AbstractClient) {
|
||||||
const client = this.clientMap.get(kwinClient);
|
const client = this.clientMap.get(kwinClient);
|
||||||
if (client === undefined) {
|
if (client === undefined) {
|
||||||
@@ -149,8 +167,8 @@ class World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clientState = client.stateManager.getState();
|
const clientState = client.stateManager.getState();
|
||||||
if (clientState instanceof ClientStateFloating && canTileEver(kwinClient)) {
|
if (clientState instanceof ClientStateFloating && Clients.canTileEver(kwinClient)) {
|
||||||
makeTileable(kwinClient);
|
Clients.makeTileable(kwinClient);
|
||||||
client.stateManager.setState(new ClientStateTiled(this, client), false);
|
client.stateManager.setState(new ClientStateTiled(this, client), false);
|
||||||
} else if (clientState instanceof ClientStateTiled) {
|
} else if (clientState instanceof ClientStateTiled) {
|
||||||
client.stateManager.setState(new ClientStateFloating(), false);
|
client.stateManager.setState(new ClientStateFloating(), false);
|
||||||
@@ -215,8 +233,8 @@ class World {
|
|||||||
destroy() {
|
destroy() {
|
||||||
this.workspaceSignalManager.destroy();
|
this.workspaceSignalManager.destroy();
|
||||||
this.removeAllClients();
|
this.removeAllClients();
|
||||||
for (const grid of this.gridManager.grids()) {
|
for (const scrollView of this.scrollViewManager.scrollViews()) {
|
||||||
grid.destroy();
|
scrollView.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user