13 Commits
v0.3 ... v0.3.1

Author SHA1 Message Date
Peter Fajdiga
57c4643098 bump version to 0.3.1 2023-08-16 21:25:17 +02:00
Peter Fajdiga
e92563b424 Grid.decreaseColumnWidth: fix scroll left bug 2023-08-16 21:19:54 +02:00
Peter Fajdiga
671326bdd7 Grid: remove unneeded arrange calls 2023-08-16 21:19:54 +02:00
Peter Fajdiga
5e9db7d2cd prevent off-screen transients (fixes #11) 2023-08-16 21:19:53 +02:00
Peter Fajdiga
b447eacdfd World: pass same config object to ScrollViewManager 2023-08-15 17:21:10 +02:00
Peter Fajdiga
94f6e6f33b Revert "remove actions gridScrollLeft and gridScrollRight"
This reverts commit bdf62b65
2023-08-15 17:18:36 +02:00
Peter Fajdiga
85b0221220 rename Actions.ts 2023-08-15 17:08:01 +02:00
Peter Fajdiga
1894b055f7 create Actions module 2023-08-15 17:07:48 +02:00
Peter Fajdiga
05f7550a3b move KwinClient util functions into Clients module 2023-08-15 17:05:32 +02:00
Peter Fajdiga
a04f629de0 Column: make width private 2023-08-13 23:34:37 +02:00
Peter Fajdiga
4bda4d0d7c Window: update skipArrange comment 2023-08-13 20:53:18 +02:00
Peter Fajdiga
8bf076948a readme: key binding configuration 2023-08-09 22:45:45 +02:00
Peter Fajdiga
04bd85a287 readme: update key bindings 2023-07-21 09:07:18 +02:00
19 changed files with 453 additions and 377 deletions

View File

@@ -22,6 +22,8 @@ Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM) an
- 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 |
@@ -37,19 +39,16 @@ Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM) an
| Meta+Shift+S | Move window down |
| Meta+Shift+Home | Move window to start |
| 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+D | Move column right |
| Meta+Ctrl+Shift+Home | Move column to start |
| Meta+Ctrl+Shift+End | Move column to end |
| Meta+Ctrl+X | Expand column (Expands focused column horizontally to fill the screen) |
| Meta+Alt++ | Expand fully visible columns (Expands fully visible columns to fill the screen) |
| Meta+Alt+- | Shrink visible columns (Shrinks fully and partially visible columns, making them fully visible and filling the screen) |
| 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+PgUp | Scroll left |
| Meta+Alt+PgDown | Scroll right |
| Meta+Alt+Home | Scroll to start |
| Meta+Alt+End | Scroll to end |
| Meta+[N] | Move focus to column N |

View File

@@ -184,7 +184,31 @@
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_manualScrollStep">
<property name="text">
<string>Manual scroll step size:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="kcfg_manualScrollStep">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="kcfg_untileOnDrag">
<property name="text">
<string>Un-tile windows by dragging them</string>
@@ -192,7 +216,7 @@
</widget>
</item>
<item row="8" column="1">
<item row="9" column="1">
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
<property name="text">
<string>Stack columns by default</string>
@@ -200,7 +224,7 @@
</widget>
</item>
<item row="9" column="1">
<item row="10" column="1">
<widget class="QCheckBox" name="kcfg_resizeNeighborColumn">
<property name="text">
<string>Resize neighbor column on edge resize</string>
@@ -208,7 +232,7 @@
</widget>
</item>
<item row="10" column="0" colspan="2">
<item row="11" column="0" colspan="2">
<spacer name="bottomSpacer_tab_general">
<property name="orientation">
<enum>Qt::Vertical</enum>

View File

@@ -9,7 +9,7 @@
}],
"Id": "karousel",
"ServiceTypes": ["KWin/Script"],
"Version": "0.3",
"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
View 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,
}
}

View File

@@ -1,341 +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.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();
});
},
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();
},
};
}
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.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();
}
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];
}
}

View File

@@ -6,6 +6,7 @@ type Config = {
gapsInnerHorizontal: number,
gapsInnerVertical: number,
overscroll: number,
manualScrollStep: number,
untileOnDrag: boolean,
stackColumnsByDefault: boolean,
resizeNeighborColumn: boolean,

View File

@@ -83,6 +83,11 @@ const configDef = [
"type": "UInt",
"default": 18
},
{
"name": "manualScrollStep",
"type": "UInt",
"default": 200
},
{
"name": "untileOnDrag",
"type": "Bool",

View File

@@ -140,6 +140,18 @@ const keyBindings: KeyBinding[] = [
"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",

View File

@@ -3,7 +3,7 @@ interface KeyBinding {
description: string;
comment?: string;
defaultKeySequence: string;
action: keyof ReturnType<typeof initActions>;
action: keyof ReturnType<typeof Actions.init>;
}
interface NumKeyBinding {
@@ -12,7 +12,7 @@ interface NumKeyBinding {
comment?: string;
defaultModifiers: string;
fKeys: boolean;
action: keyof ReturnType<typeof initNumActions>;
action: keyof ReturnType<typeof Actions.initNum>;
}
function catchWrap(f: () => void) {
@@ -49,13 +49,13 @@ function registerNumKeyBindings(name: string, description: string, modifiers: st
}
}
function registerKeyBindings(world: World) {
const actions = initActions(world);
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 = initNumActions(world);
const numActions = Actions.initNum(world);
for (const binding of numKeyBindings) {
registerNumKeyBindings(binding.name, binding.description, binding.defaultModifiers, binding.fKeys, numActions[binding.action]);
}

View File

@@ -1,7 +1,7 @@
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;

View File

@@ -72,7 +72,7 @@ class Grid {
if (firstMovedColumn !== null) {
for (const column of this.columns.iteratorFrom(firstMovedColumn)) {
column.gridX = x;
x += column.width + this.config.gapsInnerHorizontal;
x += column.getWidth() + this.config.gapsInnerHorizontal;
}
}
this.width = x - this.config.gapsInnerHorizontal;
@@ -108,7 +108,7 @@ class Grid {
let nVisible = 0;
for (const column of this.columns.iterator()) {
if (column.isVisible(scrollPos, fullyVisible)) {
width += column.width;
width += column.getWidth();
nVisible++;
}
}
@@ -140,7 +140,6 @@ class Grid {
const scrollPos = this.container.getScrollPosForColumn(column);
if (this.width < scrollPos.width) {
column.adjustWidth(scrollPos.width - this.width, false);
this.container.arrange();
return;
}
@@ -170,7 +169,6 @@ class Grid {
const scrollPos = this.container.getScrollPosForColumn(column);
if (this.width <= scrollPos.width) {
column.setWidth(Math.round(column.getWidth() / 2), false);
this.container.arrange();
return;
}
@@ -191,7 +189,8 @@ class Grid {
const shrinkLeft = leftInvisibleWidth < rightInvisibleWidth;
const widthDelta = (shrinkLeft ? leftInvisibleWidth : rightInvisibleWidth);
if (shrinkLeft) {
this.container.adjustScroll(-widthDelta, false);
const maxDelta = column.getWidth() - column.getMinWidth();
this.container.adjustScroll(-Math.min(widthDelta, maxDelta), false);
}
column.adjustWidth(-widthDelta, true);
}

View File

@@ -69,7 +69,7 @@ class ScrollView {
}
scrollCenterColumn(column: Column) {
const windowCenter = column.getLeft() + column.width / 2;
const windowCenter = column.getLeft() + column.getWidth() / 2;
const screenCenter = this.scrollX + this.tilingArea.width / 2;
this.adjustScroll(Math.round(windowCenter - screenCenter), false);
}
@@ -127,6 +127,7 @@ class ScrollView {
// 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() {

View File

@@ -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);
@@ -94,10 +94,10 @@ class Window {
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.width;
const oldNeighborWidth = neighborColumn.getWidth();
neighborColumn.adjustWidth(-widthDelta, true);
if (resizingLeftSide) {
leftEdgeDelta -= neighborColumn.width - oldNeighborWidth;
leftEdgeDelta -= neighborColumn.getWidth() - oldNeighborWidth;
}
}
}

View File

@@ -1,6 +1,6 @@
function init() {
const config = loadConfig();
const world = new World(config);
registerKeyBindings(world);
registerKeyBindings(world, config);
return world;
}

View File

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

View File

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

View File

@@ -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
View 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];
}
}
}

View File

@@ -38,12 +38,7 @@ class World {
marginRight: config.gapsOuterRight,
overscroll: config.overscroll,
},
{
gapsInnerHorizontal: config.gapsInnerHorizontal,
gapsInnerVertical: config.gapsInnerVertical,
stackColumnsByDefault: config.stackColumnsByDefault,
resizeNeighborColumn: config.resizeNeighborColumn,
},
config,
workspace.currentActivity,
workspace.desktops,
);
@@ -118,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) {
@@ -166,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);