Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
465945429a | ||
|
|
1d7636508b | ||
|
|
47213a71f5 | ||
|
|
75a548977c | ||
|
|
d746b91a88 | ||
|
|
a0d9c49287 | ||
|
|
862cc445bd | ||
|
|
5019a5d702 | ||
|
|
36c7cab137 | ||
|
|
df3c1f4512 | ||
|
|
5f3eaf1eec | ||
|
|
4a680177f6 | ||
|
|
8d807c979b | ||
|
|
c8e37aeb87 | ||
|
|
ad0fe7472c | ||
|
|
a51e45667c | ||
|
|
6615fe6f93 | ||
|
|
6e69139b80 | ||
|
|
97430d5043 | ||
|
|
47f4bbd9b6 | ||
|
|
2d4ad73d16 | ||
|
|
bb4e4f8ebd | ||
|
|
0742975334 | ||
|
|
64457429d0 | ||
|
|
02154f2f5e | ||
|
|
0a2bb4f65d | ||
|
|
6f207e59c4 | ||
|
|
4c987b6c5b | ||
|
|
bca0158df9 | ||
|
|
9feeb0f23e | ||
|
|
0241846ea5 | ||
|
|
3bf3f16f49 | ||
|
|
782a6db56d | ||
|
|
93b6850ffd | ||
|
|
5f0c637d1a | ||
|
|
8829d0b291 | ||
|
|
d37b4bc5d1 | ||
|
|
ead29e5e69 | ||
|
|
ff75d931f6 | ||
|
|
d00d514d30 | ||
|
|
3b919909dc | ||
|
|
0004b6f921 | ||
|
|
24265c56f9 | ||
|
|
dcbc0a474d | ||
|
|
88f170f5c1 | ||
|
|
78ab48ee09 | ||
|
|
b2d81796f8 | ||
|
|
7d27331ce5 | ||
|
|
55e1037a7b | ||
|
|
7820c7d00e | ||
|
|
3d8ca0bc14 | ||
|
|
eaf68b87f9 | ||
|
|
b2dfad6042 | ||
|
|
054808cb38 | ||
|
|
97059fa4f7 | ||
|
|
5e7959c7f4 |
22
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Request a feature
|
||||
title: "[Feature]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
These sections are just guidelines, feel free to remove them.
|
||||
17
README.md
17
README.md
@@ -22,6 +22,13 @@ Karousel requires the following QML modules:
|
||||
- Doesn't support windows on all desktops
|
||||
- Doesn't support windows on multiple activities
|
||||
|
||||
## Installation
|
||||
First install the _org.kde.notification_ QML module (_qml-module-org-kde-notifications_ package on Ubuntu).
|
||||
|
||||
Then download the [latest release](https://github.com/peterfajdiga/karousel/releases/latest) and extract it into _~/.local/share/kwin/scripts/_.
|
||||
|
||||
Or clone the repo and run `make install` (requires node and tsc).
|
||||
|
||||
## Key bindings
|
||||
The key bindings can be configured in KDE System Settings among KWin's own keyboard shortcuts.
|
||||
Here's the default ones:
|
||||
@@ -32,22 +39,30 @@ Here's the default ones:
|
||||
| Meta+D | Move focus right (Clashes with default KDE shortcuts, may require manual remapping) |
|
||||
| Meta+W | Move focus up (Clashes with default KDE shortcuts, may require manual remapping) |
|
||||
| Meta+S | Move focus down (Clashes with default KDE shortcuts, may require manual remapping) |
|
||||
| (unassigned) | Move focus to the next window in grid |
|
||||
| (unassigned) | Move focus to the previous window in grid |
|
||||
| Meta+Home | Move focus to start |
|
||||
| Meta+End | Move focus to end |
|
||||
| Meta+Shift+A | Move window left (Moves window out of and into columns) |
|
||||
| Meta+Shift+D | Move window right (Moves window out of and into columns) |
|
||||
| Meta+Shift+W | Move window up |
|
||||
| Meta+Shift+S | Move window down |
|
||||
| (unassigned) | Move window to the next position in grid |
|
||||
| (unassigned) | Move window to the previous position in grid |
|
||||
| Meta+Shift+Home | Move window to start |
|
||||
| Meta+Shift+End | Move window to end |
|
||||
| Meta+X | Toggle stacked layout for focused column (One window in the column visible, others shaded; not supported on Wayland) |
|
||||
| Meta+X | Toggle stacked layout for focused column (Only the active window visible) |
|
||||
| Meta+Ctrl+Shift+A | Move column left |
|
||||
| Meta+Ctrl+Shift+D | Move column right |
|
||||
| Meta+Ctrl+Shift+Home | Move column to start |
|
||||
| Meta+Ctrl+Shift+End | Move column to end |
|
||||
| Meta+Ctrl++ | Increase column width |
|
||||
| Meta+Ctrl+- | Decrease column width |
|
||||
| Meta+R | Cycle through preset column widths |
|
||||
| Meta+Shift+R | Cycle through preset column widths in reverse |
|
||||
| Meta+Ctrl+X | Equalize widths of visible columns |
|
||||
| Meta+Ctrl+A | Squeeze left column onto the screen (Clashes with default KDE shortcuts, may require manual remapping) |
|
||||
| Meta+Ctrl+D | Squeeze right column onto 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+D | Scroll one column to the right |
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<string>Stack columns by default</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>New columns start in stacked mode (one window in the column visible, others shaded). Not supported on Wayland.</string>
|
||||
<string>New columns start in stacked mode (only the active window visible)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -280,14 +280,14 @@
|
||||
</item>
|
||||
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_manualScrollStep">
|
||||
<widget class="QLabel" name="label_stackOffsetX">
|
||||
<property name="text">
|
||||
<string>Manual scroll step size:</string>
|
||||
<string>Horizontal offset for stacked columns:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QSpinBox" name="kcfg_manualScrollStep">
|
||||
<widget class="QSpinBox" name="kcfg_stackOffsetX">
|
||||
<property name="suffix">
|
||||
<string> px</string>
|
||||
</property>
|
||||
@@ -301,6 +301,48 @@
|
||||
</item>
|
||||
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_stackOffsetY">
|
||||
<property name="text">
|
||||
<string>Vertical offset for stacked columns:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QSpinBox" name="kcfg_stackOffsetY">
|
||||
<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="0">
|
||||
<widget class="QLabel" name="label_manualScrollStep">
|
||||
<property name="text">
|
||||
<string>Manual scroll step size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" 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="9" column="0">
|
||||
<widget class="QLabel" name="label_presetWidths">
|
||||
<property name="text">
|
||||
<string>Preset widths:</string>
|
||||
@@ -310,7 +352,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="9" column="1">
|
||||
<widget class="QLineEdit" name="kcfg_presetWidths">
|
||||
<property name="toolTip">
|
||||
<string>Comma-separated list of widths. Supported units: "px" and "%".</string>
|
||||
@@ -318,14 +360,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
<item row="8" column="0">
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_offScreenOpacity">
|
||||
<property name="text">
|
||||
<string>Obscured window opacity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="10" column="1">
|
||||
<widget class="QSpinBox" name="kcfg_offScreenOpacity">
|
||||
<property name="suffix">
|
||||
<string> %</string>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"Name": "Peter Fajdiga"
|
||||
}],
|
||||
"Id": "karousel",
|
||||
"Version": "0.10",
|
||||
"Version": "0.12",
|
||||
"License": "GPLv3",
|
||||
"Website": "https://github.com/peterfajdiga/karousel",
|
||||
"BugReportUrl": "https://github.com/peterfajdiga/karousel/issues"
|
||||
|
||||
@@ -11,6 +11,12 @@ class PresetWidths {
|
||||
return nextIndex >= 0 ? widths[nextIndex] : widths[0];
|
||||
}
|
||||
|
||||
public prev(currentWidth: number, minWidth: number, maxWidth: number) {
|
||||
const widths = this.getWidths(minWidth, maxWidth).reverse();
|
||||
const nextIndex = widths.findIndex(width => width < currentWidth);
|
||||
return nextIndex >= 0 ? widths[nextIndex] : widths[0];
|
||||
}
|
||||
|
||||
public getWidths(minWidth: number, maxWidth: number) {
|
||||
const widths = this.presets.map(f => clamp(f(maxWidth), minWidth, maxWidth));
|
||||
widths.sort((a, b) => a - b);
|
||||
|
||||
@@ -9,7 +9,7 @@ class ContextualResizer {
|
||||
const visibleRange = desktop.getCurrentVisibleRange();
|
||||
const minWidth = column.getMinWidth();
|
||||
const maxWidth = column.getMaxWidth();
|
||||
if(!column.isVisible(visibleRange, true) || column.getWidth() >= maxWidth) {
|
||||
if(!Range.contains(visibleRange, column) || column.getWidth() >= maxWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class ContextualResizer {
|
||||
const visibleRange = desktop.getCurrentVisibleRange();
|
||||
const minWidth = column.getMinWidth();
|
||||
const maxWidth = column.getMaxWidth();
|
||||
if(!column.isVisible(visibleRange, true) || column.getWidth() <= minWidth) {
|
||||
if(!Range.contains(visibleRange, column) || column.getWidth() <= minWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ type Config = {
|
||||
gapsOuterRight: number;
|
||||
gapsInnerHorizontal: number;
|
||||
gapsInnerVertical: number;
|
||||
stackOffsetX: number;
|
||||
stackOffsetY: number;
|
||||
manualScrollStep: number;
|
||||
presetWidths: string;
|
||||
offScreenOpacity: number;
|
||||
|
||||
@@ -3,6 +3,10 @@ const defaultWindowRules = `[
|
||||
"class": "(org\\\\.kde\\\\.)?plasmashell",
|
||||
"tile": false
|
||||
},
|
||||
{
|
||||
"class": "(org\\\\.kde\\\\.)?polkit-kde-authentication-agent-1",
|
||||
"tile": false
|
||||
},
|
||||
{
|
||||
"class": "(org\\\\.kde\\\\.)?kded6",
|
||||
"tile": false
|
||||
@@ -80,6 +84,16 @@ const configDef = [
|
||||
type: "UInt",
|
||||
default: 8,
|
||||
},
|
||||
{
|
||||
name: "stackOffsetX",
|
||||
type: "UInt",
|
||||
default: 8,
|
||||
},
|
||||
{
|
||||
name: "stackOffsetY",
|
||||
type: "UInt",
|
||||
default: 32,
|
||||
},
|
||||
{
|
||||
name: "manualScrollStep",
|
||||
type: "UInt",
|
||||
|
||||
2
src/lib/extern/kwin.ts
vendored
2
src/lib/extern/kwin.ts
vendored
@@ -54,7 +54,6 @@ type Output = { __brand: "Output" };
|
||||
type KwinClient = {
|
||||
__brand: "KwinClient";
|
||||
|
||||
readonly shadeable: boolean;
|
||||
readonly caption: string;
|
||||
readonly minSize: Readonly<QmlSize>;
|
||||
readonly transient: boolean;
|
||||
@@ -79,7 +78,6 @@ type KwinClient = {
|
||||
skipSwitcher: boolean;
|
||||
keepAbove: boolean;
|
||||
keepBelow: boolean;
|
||||
shade: boolean;
|
||||
minimized: boolean;
|
||||
frameGeometry: QmlRect;
|
||||
desktops: KwinDesktop[]; // empty array means all desktops
|
||||
|
||||
@@ -189,6 +189,11 @@ class Actions {
|
||||
column.setWidth(nextWidth, true);
|
||||
}
|
||||
|
||||
public readonly cyclePresetWidthsReverse = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
||||
const nextWidth = this.config.presetWidths.prev(column.getWidth(), column.getMinWidth(), column.getMaxWidth());
|
||||
column.setWidth(nextWidth, true);
|
||||
}
|
||||
|
||||
public readonly columnsWidthEqualize = (cm: ClientManager, dm: DesktopManager) => {
|
||||
const desktop = dm.getCurrentDesktop();
|
||||
const visibleRange = desktop.getCurrentVisibleRange();
|
||||
@@ -202,7 +207,7 @@ class Actions {
|
||||
);
|
||||
visibleColumns.forEach((column, index) => column.setWidth(widths[index], true));
|
||||
|
||||
desktop.scrollCenterRange(Desktop.RangeImpl.fromRanges(
|
||||
desktop.scrollCenterRange(Range.fromRanges(
|
||||
visibleColumns[0],
|
||||
visibleColumns[visibleColumns.length - 1],
|
||||
));
|
||||
@@ -210,7 +215,7 @@ class Actions {
|
||||
|
||||
public readonly columnsSqueezeLeft = (cm: ClientManager, dm: DesktopManager, window: Window, focusedColumn: Column, grid: Grid) => {
|
||||
const visibleRange = grid.desktop.getCurrentVisibleRange();
|
||||
if (!focusedColumn.isVisible(visibleRange, true)) {
|
||||
if (!Range.contains(visibleRange, focusedColumn)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -237,7 +242,7 @@ class Actions {
|
||||
|
||||
public readonly columnsSqueezeRight = (cm: ClientManager, dm: DesktopManager, window: Window, focusedColumn: Column, grid: Grid) => {
|
||||
const visibleRange = grid.desktop.getCurrentVisibleRange();
|
||||
if (!focusedColumn.isVisible(visibleRange, true)) {
|
||||
if (!Range.contains(visibleRange, focusedColumn)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -279,7 +284,7 @@ class Actions {
|
||||
|
||||
const widths = fillSpace(availableSpace - gapsWidth, columnConstraints);
|
||||
columns.forEach((column, index) => column.setWidth(widths[index], true));
|
||||
desktop.scrollCenterRange(Desktop.RangeImpl.fromRanges(firstColumn, lastColumn));
|
||||
desktop.scrollCenterRange(Range.fromRanges(firstColumn, lastColumn));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -292,8 +297,7 @@ class Actions {
|
||||
}
|
||||
|
||||
private readonly gridScroll = (desktopManager: DesktopManager, amount: number) => {
|
||||
const grid = desktopManager.getCurrentDesktop().grid;
|
||||
grid.desktop.adjustScroll(amount, false);
|
||||
desktopManager.getCurrentDesktop().adjustScroll(amount, false);
|
||||
}
|
||||
|
||||
public readonly gridScrollStart = (cm: ClientManager, dm: DesktopManager) => {
|
||||
@@ -302,7 +306,7 @@ class Actions {
|
||||
if (firstColumn === null) {
|
||||
return;
|
||||
}
|
||||
grid.desktop.scrollToColumn(firstColumn);
|
||||
grid.desktop.scrollToColumn(firstColumn, false);
|
||||
}
|
||||
|
||||
public readonly gridScrollEnd = (cm: ClientManager, dm: DesktopManager) => {
|
||||
@@ -311,11 +315,16 @@ class Actions {
|
||||
if (lastColumn === null) {
|
||||
return;
|
||||
}
|
||||
grid.desktop.scrollToColumn(lastColumn);
|
||||
grid.desktop.scrollToColumn(lastColumn, false);
|
||||
}
|
||||
|
||||
public readonly gridScrollFocused = (cm: ClientManager, dm: DesktopManager, window: Window, column: Column, grid: Grid) => {
|
||||
grid.desktop.scrollCenterRange(column);
|
||||
const scrollAmount = Range.minus(column, grid.desktop.getCurrentVisibleRange());
|
||||
if (scrollAmount !== 0) {
|
||||
grid.desktop.adjustScroll(scrollAmount, true);
|
||||
} else {
|
||||
grid.desktop.scrollToColumn(column, true);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly gridScrollLeftColumn = (cm: ClientManager, dm: DesktopManager) => {
|
||||
@@ -330,7 +339,7 @@ class Actions {
|
||||
return;
|
||||
}
|
||||
|
||||
grid.desktop.scrollToColumn(leftColumn);
|
||||
grid.desktop.scrollToColumn(leftColumn, false);
|
||||
}
|
||||
|
||||
public readonly gridScrollRightColumn = (cm: ClientManager, dm: DesktopManager) => {
|
||||
@@ -345,7 +354,7 @@ class Actions {
|
||||
return;
|
||||
}
|
||||
|
||||
grid.desktop.scrollToColumn(rightColumn);
|
||||
grid.desktop.scrollToColumn(rightColumn, false);
|
||||
}
|
||||
|
||||
public readonly screenSwitch = (cm: ClientManager, dm: DesktopManager) => {
|
||||
@@ -410,7 +419,10 @@ class Actions {
|
||||
namespace Actions {
|
||||
export type Config = {
|
||||
manualScrollStep: number;
|
||||
presetWidths: { next: (currentWidth: number, minWidth: number, maxWidth: number) => number };
|
||||
presetWidths: {
|
||||
next: (currentWidth: number, minWidth: number, maxWidth: number) => number;
|
||||
prev: (currentWidth: number, minWidth: number, maxWidth: number) => number
|
||||
};
|
||||
columnResizer: ColumnResizer;
|
||||
};
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ function getKeyBindings(world: World, actions: Actions): KeyBinding[] {
|
||||
{
|
||||
name: "column-toggle-stacked",
|
||||
description: "Toggle stacked layout for focused column",
|
||||
comment: "One window in the column visible, others shaded; not supported on Wayland",
|
||||
comment: "Only the active window visible",
|
||||
defaultKeySequence: "Meta+X",
|
||||
action: () => world.doIfTiledFocused(actions.columnToggleStacked),
|
||||
},
|
||||
@@ -152,6 +152,12 @@ function getKeyBindings(world: World, actions: Actions): KeyBinding[] {
|
||||
defaultKeySequence: "Meta+R",
|
||||
action: () => world.doIfTiledFocused(actions.cyclePresetWidths),
|
||||
},
|
||||
{
|
||||
name: "cycle-preset-widths-reverse",
|
||||
description: "Cycle through preset column widths in reverse",
|
||||
defaultKeySequence: "Meta+Shift+R",
|
||||
action: () => world.doIfTiledFocused(actions.cyclePresetWidthsReverse),
|
||||
},
|
||||
{
|
||||
name: "columns-width-equalize",
|
||||
description: "Equalize widths of visible columns",
|
||||
|
||||
@@ -21,7 +21,7 @@ class Column {
|
||||
if (targetGrid === this.grid) {
|
||||
this.grid.moveColumn(this, leftColumn);
|
||||
} else {
|
||||
this.grid.onColumnRemoved(this, false);
|
||||
this.grid.onColumnRemoved(this, this.isFocused());
|
||||
this.grid = targetGrid;
|
||||
targetGrid.onColumnAdded(this, leftColumn);
|
||||
for (const window of this.windows.iterator()) {
|
||||
@@ -203,50 +203,44 @@ class Column {
|
||||
window.focus();
|
||||
}
|
||||
|
||||
public isFocused() {
|
||||
const lastFocusedWindow = this.grid.getLastFocusedWindow();
|
||||
if (lastFocusedWindow === null) {
|
||||
return false;
|
||||
}
|
||||
return lastFocusedWindow.column === this && lastFocusedWindow.isFocused();
|
||||
}
|
||||
|
||||
public arrange(x: number, visibleRange: Range, forceOpaque: boolean) {
|
||||
if (this.grid.config.offScreenOpacity < 1.0 && !forceOpaque) {
|
||||
const opacity = this.isVisible(visibleRange, true) ? 100 : this.grid.config.offScreenOpacity;
|
||||
const opacity = Range.contains(visibleRange, this) ? 100 : this.grid.config.offScreenOpacity;
|
||||
for (const window of this.windows.iterator()) {
|
||||
window.client.kwinClient.opacity = opacity;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.stacked && this.windows.length() >= 2 && this.canStack()) {
|
||||
if (this.stacked && this.windows.length() >= 2) {
|
||||
this.arrangeStacked(x);
|
||||
return;
|
||||
}
|
||||
let y = this.grid.desktop.tilingArea.y;
|
||||
for (const window of this.windows.iterator()) {
|
||||
window.client.setShade(false);
|
||||
window.arrange(x, y, this.width, window.height);
|
||||
y += window.height + this.grid.config.gapsInnerVertical;
|
||||
}
|
||||
}
|
||||
|
||||
public arrangeStacked(x: number) {
|
||||
const expandedWindow = this.getFocusTaker();
|
||||
let collapsedHeight;
|
||||
for (const window of this.windows.iterator()) {
|
||||
if (window === expandedWindow) {
|
||||
window.client.setShade(false);
|
||||
} else {
|
||||
window.client.setShade(true);
|
||||
collapsedHeight = window.client.kwinClient.frameGeometry.height;
|
||||
}
|
||||
}
|
||||
const nWindows = this.windows.length();
|
||||
const windowWidth = this.width - (nWindows - 1) * this.grid.config.stackOffsetX;
|
||||
const windowHeight = this.grid.desktop.tilingArea.height - (nWindows - 1) * this.grid.config.stackOffsetY;
|
||||
|
||||
const nCollapsed = this.getWindowCount() - 1;
|
||||
const expandedHeight = this.grid.desktop.tilingArea.height - nCollapsed * (collapsedHeight! + this.grid.config.gapsInnerVertical);
|
||||
let y = this.grid.desktop.tilingArea.y;
|
||||
let windowX = x;
|
||||
let windowY = this.grid.desktop.tilingArea.y;
|
||||
for (const window of this.windows.iterator()) {
|
||||
if (window === expandedWindow) {
|
||||
window.arrange(x, y, this.width, expandedHeight);
|
||||
y += expandedHeight;
|
||||
} else {
|
||||
window.arrange(x, y, this.width, window.height);
|
||||
y += collapsedHeight!;
|
||||
}
|
||||
y += this.grid.config.gapsInnerVertical;
|
||||
window.arrange(windowX, windowY, windowWidth, windowHeight);
|
||||
windowX += this.grid.config.stackOffsetX;
|
||||
windowY += this.grid.config.stackOffsetY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,25 +252,6 @@ class Column {
|
||||
this.grid.desktop.onLayoutChanged();
|
||||
}
|
||||
|
||||
private canStack() {
|
||||
for (const window of this.windows.iterator()) {
|
||||
if (!window.client.kwinClient.shadeable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public isVisible(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||
if (fullyVisible) {
|
||||
return this.getLeft() >= visibleRange.getLeft() &&
|
||||
this.getRight() <= visibleRange.getRight();
|
||||
} else {
|
||||
return this.getRight() + this.grid.config.gapsInnerHorizontal > visibleRange.getLeft() &&
|
||||
this.getLeft() - this.grid.config.gapsInnerHorizontal < visibleRange.getRight();
|
||||
}
|
||||
}
|
||||
|
||||
public onWindowAdded(window: Window, bottom: boolean) {
|
||||
if (bottom) {
|
||||
this.windows.insertEnd(window);
|
||||
|
||||
@@ -55,7 +55,7 @@ class Desktop {
|
||||
)
|
||||
}
|
||||
|
||||
public scrollIntoView(range: Desktop.Range) {
|
||||
public scrollIntoView(range: Range) {
|
||||
const left = range.getLeft();
|
||||
const right = range.getRight();
|
||||
const initialVisibleRange = this.getCurrentVisibleRange();
|
||||
@@ -72,10 +72,9 @@ class Desktop {
|
||||
this.setScroll(targetScrollX, false);
|
||||
}
|
||||
|
||||
public scrollCenterRange(range: Desktop.Range) {
|
||||
const windowCenter = range.getLeft() + range.getWidth() / 2;
|
||||
const screenCenter = this.scrollX + this.tilingArea.width / 2;
|
||||
this.adjustScroll(Math.round(windowCenter - screenCenter), true);
|
||||
public scrollCenterRange(range: Range) {
|
||||
const scrollAmount = Range.minus(range, this.getCurrentVisibleRange());
|
||||
this.adjustScroll(scrollAmount, true);
|
||||
}
|
||||
|
||||
public scrollCenterVisible(focusedColumn: Column) {
|
||||
@@ -91,17 +90,17 @@ class Desktop {
|
||||
return;
|
||||
}
|
||||
|
||||
this.scrollToColumn(focusedColumn);
|
||||
this.scrollToColumn(focusedColumn, false);
|
||||
}
|
||||
|
||||
public scrollToColumn(column: Column) {
|
||||
if (this.dirtyScroll || !column.isVisible(this.getCurrentVisibleRange(), true)) {
|
||||
public scrollToColumn(column: Column, force: boolean) {
|
||||
if (force || this.dirtyScroll || !Range.contains(this.getCurrentVisibleRange(), column)) {
|
||||
this.config.scroller.scrollToColumn(this, column);
|
||||
}
|
||||
}
|
||||
|
||||
private getVisibleRange(scrollX: number) {
|
||||
return new Desktop.RangeImpl(scrollX, this.tilingArea.width);
|
||||
return Range.create(scrollX, this.tilingArea.width);
|
||||
}
|
||||
|
||||
public getCurrentVisibleRange() {
|
||||
@@ -135,6 +134,10 @@ class Desktop {
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
public forceArrange() {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public onLayoutChanged() {
|
||||
this.dirty = true;
|
||||
this.dirtyScroll = true;
|
||||
@@ -161,40 +164,6 @@ namespace Desktop {
|
||||
clamper: Desktop.Clamper;
|
||||
};
|
||||
|
||||
export type Range = {
|
||||
getLeft(): number;
|
||||
getRight(): number;
|
||||
getWidth(): number;
|
||||
};
|
||||
|
||||
export class RangeImpl {
|
||||
private readonly x: number;
|
||||
private 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;
|
||||
}
|
||||
|
||||
public getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public static fromRanges(leftRange: Range, rightRange: Range) {
|
||||
const left = leftRange.getLeft();
|
||||
const right = rightRange.getRight();
|
||||
return new RangeImpl(left, right - left);
|
||||
}
|
||||
}
|
||||
|
||||
export class ColumnRange {
|
||||
private left: Column;
|
||||
private right: Column;
|
||||
@@ -206,7 +175,7 @@ namespace Desktop {
|
||||
this.width = initialColumn.getWidth();
|
||||
}
|
||||
|
||||
public addNeighbors(visibleRange: Desktop.Range, gap: number) {
|
||||
public addNeighbors(visibleRange: Range, gap: number) {
|
||||
const grid = this.left.grid;
|
||||
|
||||
const columnRange = this;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import Range = Desktop.Range;
|
||||
|
||||
class Grid {
|
||||
public readonly desktop: Desktop;
|
||||
public readonly config: LayoutConfig;
|
||||
@@ -106,19 +104,19 @@ class Grid {
|
||||
this.width = x - this.config.gapsInnerHorizontal;
|
||||
}
|
||||
|
||||
public getLeftmostVisibleColumn(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||
public getLeftmostVisibleColumn(visibleRange: Range, fullyVisible: boolean) {
|
||||
for (const column of this.columns.iterator()) {
|
||||
if (column.isVisible(visibleRange, fullyVisible)) {
|
||||
if (Range.contains(visibleRange, column)) {
|
||||
return column;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getRightmostVisibleColumn(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||
public getRightmostVisibleColumn(visibleRange: Range, fullyVisible: boolean) {
|
||||
let last = null;
|
||||
for (const column of this.columns.iterator()) {
|
||||
if (column.isVisible(visibleRange, fullyVisible)) {
|
||||
if (Range.contains(visibleRange, column)) {
|
||||
last = column;
|
||||
} else if (last !== null) {
|
||||
break;
|
||||
@@ -127,29 +125,14 @@ class Grid {
|
||||
return last;
|
||||
}
|
||||
|
||||
public *getVisibleColumns(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||
public *getVisibleColumns(visibleRange: Range, fullyVisible: boolean) {
|
||||
for (const column of this.columns.iterator()) {
|
||||
if (column.isVisible(visibleRange, fullyVisible)) {
|
||||
if (Range.contains(visibleRange, column)) {
|
||||
yield column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getVisibleColumnsWidth(visibleRange: Desktop.Range, fullyVisible: boolean) {
|
||||
let width = 0;
|
||||
let nVisible = 0;
|
||||
for (const column of this.getVisibleColumns(visibleRange, fullyVisible)) {
|
||||
width += column.getWidth();
|
||||
nVisible++;
|
||||
}
|
||||
|
||||
if (nVisible > 0) {
|
||||
width += (nVisible-1) * this.config.gapsInnerHorizontal;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
public arrange(x: number, visibleRange: Range) {
|
||||
for (const column of this.columns.iterator()) {
|
||||
column.arrange(x, visibleRange, this.userResize);
|
||||
@@ -207,7 +190,7 @@ class Grid {
|
||||
lastFocusedColumn.restoreToTiled();
|
||||
}
|
||||
this.lastFocusedColumn = column;
|
||||
this.desktop.scrollToColumn(column);
|
||||
this.desktop.scrollToColumn(column, false);
|
||||
}
|
||||
|
||||
public onScreenSizeChanged() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
type LayoutConfig = {
|
||||
gapsInnerHorizontal: number;
|
||||
gapsInnerVertical: number;
|
||||
stackOffsetX: number;
|
||||
stackOffsetY: number;
|
||||
offScreenOpacity: number;
|
||||
stackColumnsByDefault: boolean;
|
||||
resizeNeighborColumn: boolean;
|
||||
|
||||
47
src/lib/layout/Range.ts
Normal file
47
src/lib/layout/Range.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
type Range = {
|
||||
getLeft(): number;
|
||||
getRight(): number;
|
||||
getWidth(): number;
|
||||
};
|
||||
|
||||
namespace Range {
|
||||
export function create(x: number, width: number) {
|
||||
return new Basic(x, width);
|
||||
}
|
||||
|
||||
export function fromRanges(leftRange: Range, rightRange: Range) {
|
||||
const left = leftRange.getLeft();
|
||||
const right = rightRange.getRight();
|
||||
return new Basic(left, right - left);
|
||||
}
|
||||
|
||||
export function contains(parent: Range, child: Range) {
|
||||
return child.getLeft() >= parent.getLeft() &&
|
||||
child.getRight() <= parent.getRight();
|
||||
}
|
||||
|
||||
export function minus(a: Range, b: Range) {
|
||||
const aCenter = a.getLeft() + a.getWidth() / 2;
|
||||
const bCenter = b.getLeft() + b.getWidth() / 2;
|
||||
return Math.round(aCenter - bCenter);
|
||||
}
|
||||
|
||||
class Basic {
|
||||
constructor(
|
||||
private readonly x: number,
|
||||
private readonly width: number,
|
||||
) {}
|
||||
|
||||
public getLeft() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public getRight() {
|
||||
return this.x + this.width;
|
||||
}
|
||||
|
||||
public getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,17 @@ class Window {
|
||||
constructor(client: ClientWrapper, column: Column) {
|
||||
this.client = client;
|
||||
this.height = client.kwinClient.frameGeometry.height;
|
||||
|
||||
let maximizedMode = this.client.getMaximizedMode();
|
||||
if (maximizedMode === undefined) {
|
||||
maximizedMode = MaximizedMode.Unmaximized; // defaulting to unmaximized, as this is set in Tiled.prepareClientForTiling
|
||||
}
|
||||
this.focusedState = {
|
||||
fullScreen: false,
|
||||
maximizedMode: MaximizedMode.Unmaximized,
|
||||
fullScreen: this.client.kwinClient.fullScreen,
|
||||
maximizedMode: maximizedMode,
|
||||
};
|
||||
this.skipArrange = false;
|
||||
|
||||
this.skipArrange = this.client.kwinClient.fullScreen || maximizedMode !== MaximizedMode.Unmaximized;
|
||||
this.column = column;
|
||||
column.onWindowAdded(this, true);
|
||||
}
|
||||
@@ -21,7 +27,7 @@ class Window {
|
||||
if (targetColumn === this.column) {
|
||||
return;
|
||||
}
|
||||
this.column.onWindowRemoved(this, false);
|
||||
this.column.onWindowRemoved(this, this.isFocused() && targetColumn.grid !== this.column.grid);
|
||||
this.column = targetColumn;
|
||||
targetColumn.onWindowAdded(this, bottom);
|
||||
}
|
||||
@@ -54,10 +60,6 @@ class Window {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
if (this.client.isShaded()) {
|
||||
// workaround for KWin deactivating clients when unshading immediately after activation
|
||||
this.client.setShade(false);
|
||||
}
|
||||
this.client.focus();
|
||||
}
|
||||
|
||||
@@ -66,6 +68,14 @@ class Window {
|
||||
}
|
||||
|
||||
public onFocused() {
|
||||
if (this.column.grid.config.reMaximize && (
|
||||
this.focusedState.maximizedMode !== MaximizedMode.Unmaximized ||
|
||||
this.focusedState.fullScreen
|
||||
)) {
|
||||
// We need to maximize/fullscreen this window, but we can't do it here.
|
||||
// We need to do it in `arrange` to ensure it happens after placement.
|
||||
this.column.grid.desktop.forceArrange();
|
||||
}
|
||||
this.column.onWindowFocused(this);
|
||||
}
|
||||
|
||||
@@ -75,7 +85,6 @@ class Window {
|
||||
}
|
||||
this.client.setFullScreen(false);
|
||||
this.client.setMaximize(false, false);
|
||||
this.column.grid.desktop.onLayoutChanged();
|
||||
}
|
||||
|
||||
public onMaximizedChanged(maximizedMode: MaximizedMode) {
|
||||
|
||||
@@ -16,6 +16,8 @@ class WindowRuleEnforcer {
|
||||
!kwinClient.transient &&
|
||||
kwinClient.managed &&
|
||||
kwinClient.pid > -1 &&
|
||||
!kwinClient.fullScreen &&
|
||||
!Clients.isFullScreenGeometry(kwinClient) &&
|
||||
!this.preferFloating.matches(kwinClient)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,38 +26,38 @@ function fillSpace(availableSpace: number, items: { min: number, max: number }[]
|
||||
}
|
||||
|
||||
function buildRanges(items: { min: number, max: number }[]) {
|
||||
const landmarks = buildLandmarks(items);
|
||||
if (landmarks.length === 1) {
|
||||
const fenceposts = extractFenceposts(items);
|
||||
if (fenceposts.length === 1) {
|
||||
return [{
|
||||
start: landmarks[0].value,
|
||||
end: landmarks[0].value,
|
||||
start: fenceposts[0].value,
|
||||
end: fenceposts[0].value,
|
||||
n: items.length,
|
||||
}];
|
||||
}
|
||||
|
||||
const ranges: Range[] = [];
|
||||
let n = 0;
|
||||
for (let i = 1; i < landmarks.length; i++) {
|
||||
const startLandmark = landmarks[i-1];
|
||||
const endLandmark = landmarks[i];
|
||||
n = n - startLandmark.nMax + startLandmark.nMin;
|
||||
for (let i = 1; i < fenceposts.length; i++) {
|
||||
const startFencepost = fenceposts[i-1];
|
||||
const endFencepost = fenceposts[i];
|
||||
n = n - startFencepost.nMax + startFencepost.nMin;
|
||||
ranges.push({
|
||||
start: startLandmark.value,
|
||||
end: endLandmark.value,
|
||||
start: startFencepost.value,
|
||||
end: endFencepost.value,
|
||||
n: n,
|
||||
});
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
function buildLandmarks(items: { min: number, max: number }[]) {
|
||||
const landmarks = new Map<number, Landmark>();
|
||||
function extractFenceposts(items: { min: number, max: number }[]) {
|
||||
const fenceposts = new Map<number, Fencepost>();
|
||||
for (const item of items) {
|
||||
mapGetOrInit(landmarks, item.min, { value: item.min, nMin: 0, nMax: 0 }).nMin++;
|
||||
mapGetOrInit(landmarks, item.max, { value: item.max, nMin: 0, nMax: 0 }).nMax++;
|
||||
mapGetOrInit(fenceposts, item.min, { value: item.min, nMin: 0, nMax: 0 }).nMin++;
|
||||
mapGetOrInit(fenceposts, item.max, { value: item.max, nMin: 0, nMax: 0 }).nMax++;
|
||||
}
|
||||
|
||||
const array = Array.from(landmarks.values());
|
||||
const array = Array.from(fenceposts.values());
|
||||
array.sort((a, b) => a.value - b.value);
|
||||
return array;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ function fillSpace(availableSpace: number, items: { min: number, max: number }[]
|
||||
n: number,
|
||||
};
|
||||
|
||||
type Landmark = {
|
||||
type Fencepost = {
|
||||
value: number,
|
||||
nMin: number,
|
||||
nMax: number,
|
||||
|
||||
@@ -35,8 +35,6 @@ class ClientManager {
|
||||
constructState = () => new ClientState.Docked(this.world, kwinClient);
|
||||
} else if (
|
||||
Clients.canTileEver(kwinClient) &&
|
||||
!kwinClient.fullScreen &&
|
||||
!Clients.isFullScreenGeometry(kwinClient) &&
|
||||
this.windowRuleEnforcer.shouldTile(kwinClient)
|
||||
) {
|
||||
Clients.makeTileable(kwinClient);
|
||||
|
||||
@@ -108,16 +108,6 @@ class ClientWrapper {
|
||||
});
|
||||
}
|
||||
|
||||
public setShade(shade: boolean) {
|
||||
this.manipulatingGeometry.do(() => {
|
||||
this.kwinClient.shade = shade;
|
||||
});
|
||||
}
|
||||
|
||||
public isShaded() {
|
||||
return this.kwinClient.shade;
|
||||
}
|
||||
|
||||
public getMaximizedMode() {
|
||||
return this.maximizedMode;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace Clients {
|
||||
];
|
||||
|
||||
export function canTileEver(kwinClient: KwinClient) {
|
||||
return kwinClient.moveable &&
|
||||
kwinClient.resizeable &&
|
||||
const shapeable = (kwinClient.moveable && kwinClient.resizeable) || kwinClient.fullScreen; // full-screen windows may become shapeable after exiting full-screen mode
|
||||
return shapeable &&
|
||||
!kwinClient.popupWindow &&
|
||||
!prohibitedClasses.includes(kwinClient.resourceClass);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class World {
|
||||
|
||||
let presetWidths = {
|
||||
next: (currentWidth: number, minWidth: number, maxWidth: number) => currentWidth,
|
||||
prev: (currentWidth: number, minWidth: number, maxWidth: number) => currentWidth,
|
||||
getWidths: (minWidth: number, maxWidth: number): number[] => [],
|
||||
};
|
||||
try {
|
||||
@@ -39,6 +40,8 @@ class World {
|
||||
const layoutConfig = {
|
||||
gapsInnerHorizontal: config.gapsInnerHorizontal,
|
||||
gapsInnerVertical: config.gapsInnerVertical,
|
||||
stackOffsetX: config.stackOffsetX,
|
||||
stackOffsetY: config.stackOffsetY,
|
||||
offScreenOpacity: config.offScreenOpacity / 100.0,
|
||||
stackColumnsByDefault: config.stackColumnsByDefault,
|
||||
resizeNeighborColumn: config.resizeNeighborColumn,
|
||||
|
||||
@@ -18,9 +18,6 @@ namespace ClientState {
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
this.signalManager.destroy();
|
||||
if (this.config.floatingKeepAbove) {
|
||||
this.client.kwinClient.keepAbove = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move to `Tiled.restoreClientAfterTiling`
|
||||
|
||||
@@ -19,9 +19,6 @@ namespace ClientState {
|
||||
|
||||
public destroy(passFocus: boolean) {
|
||||
this.signalManager.destroy();
|
||||
if (this.config.floatingKeepAbove) {
|
||||
this.kwinClient.keepAbove = true;
|
||||
}
|
||||
this.pinManager.removeClient(this.kwinClient);
|
||||
for (const desktop of this.desktopManager.getDesktopsForClient(this.kwinClient)) {
|
||||
desktop.onPinsChanged();
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace ClientState {
|
||||
});
|
||||
});
|
||||
|
||||
let moving = false;
|
||||
let resizing = false;
|
||||
let resizeStartWidth = 0;
|
||||
let resizeNeighbor: { column: Column, startWidth: number } | undefined;
|
||||
@@ -78,6 +79,8 @@ namespace ClientState {
|
||||
world.do((clientManager, desktopManager) => {
|
||||
clientManager.floatClient(client);
|
||||
});
|
||||
} else {
|
||||
moving = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -99,6 +102,10 @@ namespace ClientState {
|
||||
});
|
||||
|
||||
manager.connect(kwinClient.interactiveMoveResizeFinished, () => {
|
||||
if (moving) {
|
||||
moving = false;
|
||||
world.do(() => window.column.grid.desktop.onLayoutChanged()); // move the dragged window back to its position
|
||||
}
|
||||
if (resizing) {
|
||||
resizing = false;
|
||||
resizeNeighbor = undefined;
|
||||
@@ -160,7 +167,15 @@ namespace ClientState {
|
||||
});
|
||||
|
||||
manager.connect(kwinClient.fullScreenChanged, () => {
|
||||
world.do(() => window.onFullScreenChanged(kwinClient.fullScreen));
|
||||
world.do((clientManager, desktopManager) => {
|
||||
// some clients only turn out to be untileable after exiting full-screen mode
|
||||
if (!Clients.canTileEver(kwinClient)) {
|
||||
clientManager.floatClient(client);
|
||||
return;
|
||||
}
|
||||
|
||||
window.onFullScreenChanged(kwinClient.fullScreen);
|
||||
});
|
||||
});
|
||||
|
||||
manager.connect(kwinClient.tileChanged, () => {
|
||||
@@ -204,7 +219,7 @@ namespace ClientState {
|
||||
if (config.tiledKeepBelow) {
|
||||
client.kwinClient.keepBelow = true;
|
||||
}
|
||||
client.setFullScreen(false);
|
||||
client.kwinClient.keepAbove = false;
|
||||
if (client.kwinClient.tile !== null) {
|
||||
client.setMaximize(false, true); // disable quick tile mode
|
||||
}
|
||||
@@ -221,7 +236,6 @@ namespace ClientState {
|
||||
if (config.offScreenOpacity < 1.0) {
|
||||
client.kwinClient.opacity = 1.0;
|
||||
}
|
||||
client.setShade(false);
|
||||
client.setFullScreen(false);
|
||||
if (client.kwinClient.tile === null) {
|
||||
client.setMaximize(false, false);
|
||||
|
||||
43
src/tests/flows/centerFocused.ts
Normal file
43
src/tests/flows/centerFocused.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
tests.register("Center focused", 1, () => {
|
||||
const config = getDefaultConfig();
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const [client0, client1, client2] = workspaceMock.createClientsWithWidths(300, 152, 300);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.hasClient(client0));
|
||||
Assert.assert(clientManager.hasClient(client1));
|
||||
Assert.assert(clientManager.hasClient(client2));
|
||||
});
|
||||
Assert.assert(workspaceMock.activeWindow === client2);
|
||||
Assert.columnsFillTilingArea([client0, client1, client2]);
|
||||
|
||||
// center client2
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
Assert.centered(config, tilingArea, client2);
|
||||
Assert.fullyVisible(client1.frameGeometry);
|
||||
Assert.fullyVisible(client2.frameGeometry);
|
||||
|
||||
// undo center client2
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
Assert.columnsFillTilingArea([client0, client1, client2]);
|
||||
|
||||
// center client2
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
Assert.centered(config, tilingArea, client2);
|
||||
Assert.fullyVisible(client1.frameGeometry);
|
||||
Assert.fullyVisible(client2.frameGeometry);
|
||||
|
||||
// focus client1 (no scrolling should occur)
|
||||
qtMock.fireShortcut("karousel-focus-left");
|
||||
Assert.centered(config, tilingArea, client2, { message: "No scrolling should have occured" });
|
||||
Assert.fullyVisible(client1.frameGeometry);
|
||||
Assert.fullyVisible(client2.frameGeometry);
|
||||
|
||||
// center client1
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
Assert.columnsFillTilingArea([client0, client1, client2]);
|
||||
|
||||
// undo center client1 (no scrolling should occur, because all clients are already visible and centered)
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
Assert.columnsFillTilingArea([client0, client1, client2]);
|
||||
});
|
||||
34
src/tests/flows/dragTiled.ts
Normal file
34
src/tests/flows/dragTiled.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
tests.register("Drag tiled window, untile", 20, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.untileOnDrag = true;
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
const clientManager = getClientManager(world);
|
||||
|
||||
const [client0, client1] = workspaceMock.createClients(2);
|
||||
Assert.tiledClient(clientManager, client0);
|
||||
Assert.tiledClient(clientManager, client1);
|
||||
Assert.grid(config, tilingArea, 100, [[client0], [client1]], true);
|
||||
|
||||
workspaceMock.moveWindow(client0, new MockQmlPoint(10, 10));
|
||||
Assert.notTiledClient(clientManager, client0);
|
||||
Assert.tiledClient(clientManager, client1);
|
||||
Assert.grid(config, tilingArea, 100, [[client1]], true);
|
||||
});
|
||||
|
||||
tests.register("Drag tiled window, keep tiled", 20, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.untileOnDrag = false;
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
const clientManager = getClientManager(world);
|
||||
|
||||
const [client0, client1] = workspaceMock.createClients(2);
|
||||
Assert.tiledClient(clientManager, client0);
|
||||
Assert.tiledClient(clientManager, client1);
|
||||
Assert.grid(config, tilingArea, 100, [[client0], [client1]], true);
|
||||
|
||||
const move = new MockQmlPoint(10, 10);
|
||||
workspaceMock.moveWindow(client0, move, move, move, move, move, move, move, move, move); // many moves in order to trigger externalFrameGeometryChangedRateLimiter
|
||||
Assert.tiledClient(clientManager, client0);
|
||||
Assert.tiledClient(clientManager, client1);
|
||||
Assert.grid(config, tilingArea, 100, [[client0], [client1]], true);
|
||||
});
|
||||
@@ -8,7 +8,7 @@ tests.register("External resize", 1, () => {
|
||||
|
||||
function getTiledFrame(width: number) {
|
||||
return new MockQmlRect(
|
||||
Math.round((screen.width - width) / 2),
|
||||
tilingArea.left + Math.round((tilingArea.width - width) / 2),
|
||||
tilingArea.top,
|
||||
width,
|
||||
tilingArea.height,
|
||||
|
||||
191
src/tests/flows/layering.ts
Normal file
191
src/tests/flows/layering.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
tests.register("tiledKeepBelow", 10, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.tiledKeepBelow = true;
|
||||
config.floatingKeepAbove = false;
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const pinGeometry = new MockQmlRect(0, 0, 200, screen.height);
|
||||
|
||||
const [client] = workspaceMock.createClients(1);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
||||
});
|
||||
Assert.assert(client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
|
||||
client.pin(pinGeometry);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
|
||||
client.unpin();
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
||||
});
|
||||
Assert.assert(client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
|
||||
client.pin(pinGeometry);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
||||
});
|
||||
Assert.assert(client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
});
|
||||
|
||||
tests.register("floatingKeepAbove", 10, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.tiledKeepBelow = false;
|
||||
config.floatingKeepAbove = true;
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const pinGeometry = new MockQmlRect(0, 0, 200, screen.height);
|
||||
|
||||
const [client] = workspaceMock.createClients(1);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(client.keepAbove);
|
||||
|
||||
client.pin(pinGeometry);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(client.keepAbove);
|
||||
|
||||
client.unpin();
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(client.keepAbove);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
|
||||
client.pin(pinGeometry);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(client.keepAbove);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) !== null);
|
||||
});
|
||||
Assert.assert(!client.keepBelow);
|
||||
Assert.assert(!client.keepAbove);
|
||||
});
|
||||
|
||||
tests.register("No layering", 10, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.tiledKeepBelow = false;
|
||||
config.floatingKeepAbove = false;
|
||||
// In this mode, Karousel shouldn't change keepBelow or keepAbove.
|
||||
// Except when tiling a window, keepAbove should still be cleared.
|
||||
|
||||
const pinGeometry = new MockQmlRect(0, 0, 200, screen.height);
|
||||
|
||||
const testCases = [
|
||||
{ keepBelow: false, keepAbove: false },
|
||||
{ keepBelow: false, keepAbove: true },
|
||||
{ keepBelow: true, keepAbove: false },
|
||||
{ keepBelow: true, keepAbove: true },
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
const assertOptions = { message: JSON.stringify(testCase) };
|
||||
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const [client] = workspaceMock.createClients(1);
|
||||
client.keepBelow = testCase.keepBelow;
|
||||
client.keepAbove = testCase.keepAbove;
|
||||
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) !== null, assertOptions);
|
||||
});
|
||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null, assertOptions);
|
||||
});
|
||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
||||
|
||||
client.pin(pinGeometry);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null, assertOptions);
|
||||
});
|
||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
||||
|
||||
client.unpin();
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null, assertOptions);
|
||||
});
|
||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) !== null, assertOptions);
|
||||
});
|
||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
||||
Assert.assert(!client.keepAbove, assertOptions);
|
||||
client.keepAbove = testCase.keepAbove;
|
||||
|
||||
client.pin(pinGeometry);
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) === null, assertOptions);
|
||||
});
|
||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
||||
Assert.equal(client.keepAbove, testCase.keepAbove, assertOptions);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.findTiledWindow(client) !== null, assertOptions);
|
||||
});
|
||||
Assert.equal(client.keepBelow, testCase.keepBelow, assertOptions);
|
||||
Assert.assert(!client.keepAbove, assertOptions);
|
||||
}
|
||||
});
|
||||
@@ -12,7 +12,7 @@ tests.register("Focus and move windows", 1, () => {
|
||||
|
||||
function testLayout(shortcutName: string, grid: KwinClient[][]) {
|
||||
qtMock.fireShortcut(shortcutName);
|
||||
Assert.grid(config, screen, grid, { skip: 1 });
|
||||
Assert.grid(config, tilingArea, 100, grid, true, [], { skip: 1 });
|
||||
}
|
||||
|
||||
function testFocus(shortcutName: string, expectedFocus: KwinClient) {
|
||||
|
||||
47
src/tests/flows/lazyScroller.ts
Normal file
47
src/tests/flows/lazyScroller.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
tests.register("LazyScroller", 20, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.scrollingLazy = true;
|
||||
config.scrollingCentered = false;
|
||||
config.scrollingGrouped = false;
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const [client1] = workspaceMock.createClientsWithWidths(300);
|
||||
Assert.grid(config, tilingArea, 300, [[client1]], true);
|
||||
|
||||
const [client2] = workspaceMock.createClientsWithWidths(300);
|
||||
Assert.grid(config, tilingArea, 300, [[client1], [client2]], true);
|
||||
|
||||
const [client3] = workspaceMock.createClientsWithWidths(300);
|
||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
||||
Assert.equal(client3.frameGeometry.right, tilingArea.right);
|
||||
|
||||
runOneOf(
|
||||
() => workspaceMock.activeWindow = client2,
|
||||
() => qtMock.fireShortcut("karousel-focus-2"),
|
||||
() => qtMock.fireShortcut("karousel-focus-left"),
|
||||
);
|
||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
||||
Assert.equal(client3.frameGeometry.right, tilingArea.right);
|
||||
|
||||
runOneOf(
|
||||
() => workspaceMock.activeWindow = client1,
|
||||
() => qtMock.fireShortcut("karousel-focus-1"),
|
||||
() => qtMock.fireShortcut("karousel-focus-left"),
|
||||
() => qtMock.fireShortcut("karousel-focus-start"),
|
||||
);
|
||||
workspaceMock.activeWindow = client1;
|
||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
||||
Assert.equal(client1.frameGeometry.left, tilingArea.left);
|
||||
|
||||
qtMock.fireShortcut("karousel-grid-scroll-focused");
|
||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
||||
Assert.grid(config, tilingArea, 300, [[client1]], true);
|
||||
|
||||
runOneOf(
|
||||
() => workspaceMock.activeWindow = client2,
|
||||
() => qtMock.fireShortcut("karousel-focus-2"),
|
||||
() => qtMock.fireShortcut("karousel-focus-right"),
|
||||
);
|
||||
Assert.grid(config, tilingArea, 300, [[client1], [client2], [client3]], false);
|
||||
Assert.equal(client1.frameGeometry.left, tilingArea.left);
|
||||
});
|
||||
@@ -7,7 +7,7 @@ tests.register("Maximization", 100, () => {
|
||||
Assert.assert(clientManager.hasClient(kwinClient));
|
||||
});
|
||||
|
||||
const columnLeftX = screen.width/2 - 300/2;
|
||||
const columnLeftX = tilingArea.left + tilingArea.width/2 - 300/2;
|
||||
const columnTopY = tilingArea.top;
|
||||
const columnHeight = tilingArea.height;
|
||||
Assert.rect(kwinClient.frameGeometry, columnLeftX, columnTopY, 300, columnHeight);
|
||||
@@ -66,7 +66,7 @@ tests.register("Re-maximize disabled", 100, () => {
|
||||
});
|
||||
|
||||
const columnsWidth = 300 + 400 + config.gapsInnerHorizontal;
|
||||
const column1LeftX = screen.width/2 - columnsWidth/2;
|
||||
const column1LeftX = tilingArea.left + tilingArea.width/2 - columnsWidth/2;
|
||||
const column2LeftX = column1LeftX + 300 + config.gapsInnerHorizontal;
|
||||
const columnTopY = tilingArea.top;
|
||||
const columnHeight = tilingArea.height;
|
||||
@@ -111,7 +111,7 @@ tests.register("Re-maximize enabled", 100, () => {
|
||||
});
|
||||
|
||||
const columnsWidth = 300 + 400 + config.gapsInnerHorizontal;
|
||||
const column1LeftX = screen.width/2 - columnsWidth/2;
|
||||
const column1LeftX = tilingArea.left + tilingArea.width/2 - columnsWidth/2;
|
||||
const column2LeftX = column1LeftX + 300 + config.gapsInnerHorizontal;
|
||||
const columnTopY = tilingArea.top;
|
||||
const columnHeight = tilingArea.height;
|
||||
@@ -143,3 +143,72 @@ tests.register("Re-maximize enabled", 100, () => {
|
||||
Assert.rect(client1.frameGeometry, column1LeftX, columnTopY, 300, columnHeight);
|
||||
Assert.equalRects(client2.frameGeometry, screen);
|
||||
});
|
||||
|
||||
tests.register("Start full-screen", 100, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.reMaximize = true;
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const [windowedClient] = workspaceMock.createClientsWithWidths(300);
|
||||
const fullScreenClient = new MockKwinClient(new MockQmlRect(0, 0, 400, 200));
|
||||
fullScreenClient.resourceClass = "full-screen-app";
|
||||
fullScreenClient.fullScreen = true;
|
||||
workspaceMock.createWindows(fullScreenClient);
|
||||
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.hasClient(windowedClient));
|
||||
Assert.assert(clientManager.hasClient(fullScreenClient));
|
||||
});
|
||||
|
||||
Assert.centered(config, tilingArea, windowedClient);
|
||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
||||
Assert.equal(Workspace.activeWindow, fullScreenClient);
|
||||
|
||||
{
|
||||
qtMock.fireShortcut("karousel-focus-left");
|
||||
const opts = { message: "fullScreenClient is not in the grid, so we can't move focus directionally" };
|
||||
Assert.centered(config, tilingArea, windowedClient, opts);
|
||||
Assert.equalRects(fullScreenClient.frameGeometry, screen, opts);
|
||||
Assert.equal(Workspace.activeWindow, fullScreenClient, opts);
|
||||
}
|
||||
|
||||
{
|
||||
qtMock.fireShortcut("karousel-focus-1");
|
||||
const opts = { message: "fullScreenClient is not in grid, so it should stay full-screen" };
|
||||
Assert.centered(config, tilingArea, windowedClient, opts);
|
||||
Assert.equalRects(fullScreenClient.frameGeometry, screen, opts);
|
||||
Assert.equal(Workspace.activeWindow, windowedClient);
|
||||
}
|
||||
});
|
||||
|
||||
tests.register("Start full-screen (force tiling)", 100, () => {
|
||||
const config = getDefaultConfig();
|
||||
config.reMaximize = true;
|
||||
config.windowRules = '[{ "class": "full-screen-app", "tile": true }]';
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const column1Width = 300;
|
||||
const [windowedClient] = workspaceMock.createClientsWithWidths(column1Width);
|
||||
const fullScreenClient = new MockKwinClient(new MockQmlRect(0, 0, 400, 200));
|
||||
fullScreenClient.resourceClass = "full-screen-app";
|
||||
fullScreenClient.fullScreen = true;
|
||||
workspaceMock.createWindows(fullScreenClient);
|
||||
|
||||
world.do((clientManager, desktopManager) => {
|
||||
Assert.assert(clientManager.hasClient(windowedClient));
|
||||
Assert.assert(clientManager.hasClient(fullScreenClient));
|
||||
});
|
||||
Assert.equalRects(fullScreenClient.frameGeometry, screen);
|
||||
Assert.equal(Workspace.activeWindow, fullScreenClient);
|
||||
|
||||
const column2Width = tilingArea.width;
|
||||
const column1LeftX = tilingArea.left;
|
||||
const column2LeftX = column1LeftX + column1Width + gapH;
|
||||
const columnTopY = tilingArea.top;
|
||||
const columnHeight = tilingArea.height;
|
||||
qtMock.fireShortcut("karousel-focus-left");
|
||||
const opts = { message: "fullScreenClient should be restored from full-screen mode to tiled mode" };
|
||||
Assert.rect(windowedClient.frameGeometry, column1LeftX, columnTopY, column1Width, columnHeight, opts);
|
||||
Assert.rect(fullScreenClient.frameGeometry, column2LeftX, columnTopY, column2Width, columnHeight, opts);
|
||||
Assert.equal(Workspace.activeWindow, windowedClient);
|
||||
});
|
||||
|
||||
40
src/tests/flows/passFocus.ts
Normal file
40
src/tests/flows/passFocus.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
tests.register("Pass focus", 20, () => {
|
||||
const config = getDefaultConfig();
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const [client0, client1a, client1b, client1c, client4, client5, client6] = workspaceMock.createClients(7);
|
||||
workspaceMock.activeWindow = client1b;
|
||||
qtMock.fireShortcut("karousel-window-move-left");
|
||||
workspaceMock.activeWindow = client1c;
|
||||
qtMock.fireShortcut("karousel-window-move-left");
|
||||
workspaceMock.activeWindow = client1b;
|
||||
workspaceMock.activeWindow = client5;
|
||||
|
||||
function removeWindow(client: MockKwinClient) {
|
||||
runOneOf(
|
||||
() => workspaceMock.removeWindow(client),
|
||||
() => client.desktops = [workspaceMock.desktops[1]],
|
||||
);
|
||||
}
|
||||
|
||||
removeWindow(client5);
|
||||
Assert.equal(workspaceMock.activeWindow, client4);
|
||||
|
||||
qtMock.fireShortcut("karousel-column-move-to-desktop-2");
|
||||
Assert.equal(workspaceMock.activeWindow, client1b);
|
||||
|
||||
removeWindow(client1b);
|
||||
Assert.equal(workspaceMock.activeWindow, client1a);
|
||||
|
||||
removeWindow(client1a);
|
||||
Assert.equal(workspaceMock.activeWindow, client1c);
|
||||
|
||||
removeWindow(client1c);
|
||||
Assert.equal(workspaceMock.activeWindow, client0);
|
||||
|
||||
removeWindow(client0);
|
||||
Assert.equal(workspaceMock.activeWindow, client6);
|
||||
|
||||
removeWindow(client6);
|
||||
Assert.equal(workspaceMock.activeWindow, null);
|
||||
});
|
||||
@@ -5,35 +5,48 @@ tests.register("Pin", 20, () => {
|
||||
const screenHalfLeft = new MockQmlRect(0, 0, screen.width/2, screen.height);
|
||||
const screenHalfRight = new MockQmlRect(screen.width/2, 0, screen.width/2, screen.height);
|
||||
|
||||
const tilingAreaHalfLeft = new MockQmlRect(
|
||||
tilingArea.x,
|
||||
tilingArea.y,
|
||||
screen.width/2 - config.gapsOuterLeft - config.gapsOuterRight,
|
||||
tilingArea.height,
|
||||
);
|
||||
const tilingAreaHalfRight = new MockQmlRect(
|
||||
screen.width/2 + config.gapsOuterLeft,
|
||||
tilingArea.y,
|
||||
screen.width/2 - config.gapsOuterLeft - config.gapsOuterRight,
|
||||
tilingArea.height,
|
||||
);
|
||||
|
||||
const [pinned, tiled1, tiled2] = workspaceMock.createClients(3);
|
||||
Assert.grid(config, screen, [ [pinned], [tiled1], [tiled2] ]);
|
||||
Assert.grid(config, tilingArea, 100, [ [pinned], [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.pin(screenHalfLeft);
|
||||
Assert.equalRects(pinned.frameGeometry, screenHalfLeft);
|
||||
Assert.grid(config, screenHalfRight, [ [tiled1], [tiled2] ]);
|
||||
Assert.grid(config, tilingAreaHalfRight, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.pin(screenHalfRight);
|
||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
||||
Assert.grid(config, screenHalfLeft, [ [tiled1], [tiled2] ]);
|
||||
Assert.grid(config, tilingAreaHalfLeft, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.unpin();
|
||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
||||
Assert.grid(config, screen, [ [tiled1], [tiled2] ]);
|
||||
Assert.grid(config, tilingArea, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.pin(screenHalfRight);
|
||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
||||
Assert.grid(config, screenHalfLeft, [ [tiled1], [tiled2] ]);
|
||||
Assert.grid(config, tilingAreaHalfLeft, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.minimized = true;
|
||||
Assert.grid(config, screen, [ [tiled1], [tiled2] ]);
|
||||
Assert.grid(config, tilingArea, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
pinned.minimized = false;
|
||||
Assert.equalRects(pinned.frameGeometry, screenHalfRight);
|
||||
Assert.grid(config, screenHalfLeft, [ [tiled1], [tiled2] ]);
|
||||
Assert.grid(config, tilingAreaHalfLeft, 100, [ [tiled1], [tiled2] ], true);
|
||||
|
||||
workspaceMock.activeWindow = pinned;
|
||||
qtMock.fireShortcut("karousel-window-toggle-floating");
|
||||
Assert.assert(pinned.tile === null);
|
||||
pinned.frameGeometry = new MockQmlRect(10, 20, 100, 200); // This is needed because the window's preferredWidth can change when pinning, because frameGeometryChanged can fire before tileChanged. TODO: Ensure pinned window keeps its preferredWidth.
|
||||
Assert.grid(config, screen, [ [tiled1], [tiled2], [pinned] ]);
|
||||
Assert.grid(config, tilingArea, 100, [ [tiled1], [tiled2], [pinned] ], true);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ tests.register("Preset Widths default", 1, () => {
|
||||
|
||||
function getRect(columnWidth: number) {
|
||||
return new MockQmlRect(
|
||||
(screen.width - columnWidth) / 2,
|
||||
tilingArea.left + (tilingArea.width - columnWidth) / 2,
|
||||
tilingArea.top,
|
||||
columnWidth,
|
||||
tilingArea.height,
|
||||
@@ -25,6 +25,12 @@ tests.register("Preset Widths default", 1, () => {
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(maxWidth));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
||||
});
|
||||
|
||||
tests.register("Preset Widths custom", 1, () => {
|
||||
@@ -37,7 +43,7 @@ tests.register("Preset Widths custom", 1, () => {
|
||||
|
||||
function getRect(columnWidth: number) {
|
||||
return new MockQmlRect(
|
||||
(screen.width - columnWidth) / 2,
|
||||
tilingArea.left + (tilingArea.width - columnWidth) / 2,
|
||||
tilingArea.top,
|
||||
columnWidth,
|
||||
tilingArea.height,
|
||||
@@ -61,6 +67,15 @@ tests.register("Preset Widths custom", 1, () => {
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(250));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(100));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(500));
|
||||
|
||||
qtMock.fireShortcut("karousel-cycle-preset-widths-reverse");
|
||||
Assert.equalRects(kwinClient.frameGeometry, getRect(halfWidth));
|
||||
});
|
||||
|
||||
tests.register("Preset Widths fill screen uniform", 1, () => {
|
||||
|
||||
30
src/tests/flows/stacked.ts
Normal file
30
src/tests/flows/stacked.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
tests.register("Stacked", 5, () => {
|
||||
const config = getDefaultConfig();
|
||||
const { qtMock, workspaceMock, world } = init(config);
|
||||
|
||||
const [leftTop, leftBottom, rightTop, rightBottom] = workspaceMock.createClients(4);
|
||||
const grid = [[leftTop, leftBottom], [rightTop, rightBottom]];
|
||||
workspaceMock.activeWindow = rightBottom;
|
||||
qtMock.fireShortcut("karousel-window-move-left");
|
||||
workspaceMock.activeWindow = leftBottom;
|
||||
qtMock.fireShortcut("karousel-window-move-left");
|
||||
Assert.grid(config, tilingArea, 100, grid, true);
|
||||
|
||||
qtMock.fireShortcut("karousel-column-toggle-stacked");
|
||||
Assert.grid(config, tilingArea, 100, grid, true, [0]);
|
||||
|
||||
qtMock.fireShortcut("karousel-focus-up");
|
||||
Assert.grid(config, tilingArea, 100, grid, true, [0]);
|
||||
|
||||
qtMock.fireShortcut("karousel-focus-down");
|
||||
Assert.grid(config, tilingArea, 100, grid, true, [0]);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-move-up");
|
||||
Assert.grid(config, tilingArea, 100, [[leftBottom, leftTop], [rightTop, rightBottom]], true, [0]);
|
||||
|
||||
qtMock.fireShortcut("karousel-window-move-down");
|
||||
Assert.grid(config, tilingArea, 100, grid, true, [0]);
|
||||
|
||||
qtMock.fireShortcut("karousel-column-toggle-stacked");
|
||||
Assert.grid(config, tilingArea, 100, grid, true);
|
||||
});
|
||||
@@ -1,4 +1,7 @@
|
||||
tests.register("WindowRuleEnforcer", 1, () => {
|
||||
screen = new MockQmlRect(0, 0, 800, 600);
|
||||
Workspace = new MockWorkspace();
|
||||
|
||||
const testCases = [
|
||||
{ tiledByDefault: true, resourceClass: "unknown", caption: "anything", shouldTile: true },
|
||||
{ tiledByDefault: false, resourceClass: "unknown", caption: "anything", shouldTile: false },
|
||||
@@ -25,6 +28,7 @@ tests.register("WindowRuleEnforcer", 1, () => {
|
||||
return {
|
||||
normalWindow: normalWindow,
|
||||
transient: false,
|
||||
clientGeometry: new MockQmlRect(0, 0, 200, 200),
|
||||
managed: true,
|
||||
pid: 100,
|
||||
moveable: true,
|
||||
|
||||
@@ -4,6 +4,7 @@ tests.register("Clients.canTileEver", 1, () => {
|
||||
{ clientProperties: { resourceClass: "app", caption: "Title", moveable: false }, tileable: false },
|
||||
{ clientProperties: { resourceClass: "app", caption: "Caption", resizeable: false }, tileable: false },
|
||||
{ clientProperties: { resourceClass: "app", caption: "Caption", normalWindow: false, popupWindow: true }, tileable: false },
|
||||
{ clientProperties: { resourceClass: "app", caption: "Caption", moveable: false, resizeable: false, fullScreen: true }, tileable: true },
|
||||
{ clientProperties: { resourceClass: "ksmserver-logout-greeter", caption: "Caption" }, tileable: false },
|
||||
{ clientProperties: { resourceClass: "xwaylandvideobridge", caption: "" }, tileable: false },
|
||||
];
|
||||
@@ -24,6 +25,7 @@ tests.register("Clients.canTileEver", 1, () => {
|
||||
pid: 100,
|
||||
moveable: true,
|
||||
resizeable: true,
|
||||
fullScreen: false,
|
||||
popupWindow: false,
|
||||
minimized: false,
|
||||
desktops: [1],
|
||||
|
||||
@@ -125,38 +125,73 @@ namespace Assert {
|
||||
|
||||
export function grid(
|
||||
config: Config,
|
||||
screen: QmlRect,
|
||||
tilingArea: QmlRect,
|
||||
columnWidth: number,
|
||||
grid: KwinClient[][],
|
||||
centered: boolean,
|
||||
stackedColumns: number[] = [],
|
||||
{ message, skip=0 }: Options = {},
|
||||
) {
|
||||
// assumes uniformly sized windows within columns of width 100
|
||||
const nColumns = grid.length;
|
||||
const columnsWidth = nColumns * columnWidth + (nColumns-1) * config.gapsInnerHorizontal;
|
||||
const startX = centered ?
|
||||
tilingArea.x + (tilingArea.width - columnsWidth) / 2 :
|
||||
grid[0][0].frameGeometry.x;
|
||||
|
||||
// assumes uniformly sized windows within columns of uniform width
|
||||
function getRectInGrid(column: number, window: number, nColumns: number, nWindows: number) {
|
||||
const columnHeight = screen.height - config.gapsOuterTop - config.gapsOuterBottom;
|
||||
const columnsWidth = nColumns * 100 + (nColumns-1) * config.gapsInnerHorizontal;
|
||||
const windowHeight = (columnHeight - config.gapsInnerVertical * (nWindows-1)) / nWindows;
|
||||
const windowHeight = (tilingArea.height - config.gapsInnerVertical * (nWindows-1)) / nWindows;
|
||||
return new MockQmlRect(
|
||||
screen.x + column * (100 + config.gapsInnerHorizontal) + (screen.width-columnsWidth) / 2,
|
||||
screen.y + config.gapsOuterTop + (windowHeight + config.gapsInnerVertical) * window,
|
||||
100,
|
||||
(columnHeight - config.gapsInnerVertical * (nWindows-1)) / nWindows,
|
||||
startX + column * (columnWidth + config.gapsInnerHorizontal),
|
||||
tilingArea.y + (windowHeight + config.gapsInnerVertical) * window,
|
||||
columnWidth,
|
||||
(tilingArea.height - config.gapsInnerVertical * (nWindows-1)) / nWindows,
|
||||
);
|
||||
}
|
||||
|
||||
function getRectInGridStacked(column: number, window: number, nColumns: number, nWindows: number) {
|
||||
const columnX = startX + column * (columnWidth + config.gapsInnerHorizontal);
|
||||
return new MockQmlRect(
|
||||
columnX + window * config.stackOffsetX,
|
||||
tilingArea.y + window * config.stackOffsetY,
|
||||
columnWidth - (nWindows-1) * config.stackOffsetX,
|
||||
tilingArea.height - (nWindows-1) * config.stackOffsetY,
|
||||
);
|
||||
}
|
||||
|
||||
const nColumns = grid.length;
|
||||
for (let iColumn = 0; iColumn < nColumns; iColumn++) {
|
||||
const column = grid[iColumn];
|
||||
const stacked = stackedColumns.includes(iColumn);
|
||||
const getRect = stacked ? getRectInGridStacked : getRectInGrid;
|
||||
const nWindows = column.length;
|
||||
for (let iWindow = 0; iWindow < nWindows; iWindow++) {
|
||||
const window = column[iWindow];
|
||||
equalRects(
|
||||
window.frameGeometry,
|
||||
getRectInGrid(iColumn, iWindow, nColumns, nWindows),
|
||||
{ message: appendMessage(`window ${iWindow}, column ${iColumn}`, message), skip: skip+1 },
|
||||
getRect(iColumn, iWindow, nColumns, nWindows),
|
||||
{ message: appendMessage(`column ${iColumn}, window ${iWindow}`, message), skip: skip+1 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function centered(
|
||||
config: Config,
|
||||
tilingArea: QmlRect,
|
||||
client:KwinClient,
|
||||
{ message, skip=0 }: Options = {},
|
||||
) {
|
||||
grid(
|
||||
config,
|
||||
tilingArea,
|
||||
client.frameGeometry.width,
|
||||
[[client]],
|
||||
true,
|
||||
[],
|
||||
{ message: appendMessage("Window not centered", message), skip: skip+1 },
|
||||
);
|
||||
}
|
||||
|
||||
export function fullyVisible(
|
||||
rect: QmlRect,
|
||||
{ message, skip=0 }: Options = {},
|
||||
@@ -197,4 +232,26 @@ namespace Assert {
|
||||
}
|
||||
equal(columns[columns.length-1].frameGeometry.right, tilingArea.right, options);
|
||||
}
|
||||
|
||||
export function tiledClient(
|
||||
clientManager: ClientManager,
|
||||
client: KwinClient,
|
||||
{ message, skip=0 }: Options = {},
|
||||
) {
|
||||
assert(
|
||||
clientManager.findTiledWindow(client) !== null,
|
||||
{ message: message, skip: skip+1 },
|
||||
);
|
||||
}
|
||||
|
||||
export function notTiledClient(
|
||||
clientManager: ClientManager,
|
||||
client: KwinClient,
|
||||
{ message, skip=0 }: Options = {},
|
||||
) {
|
||||
assert(
|
||||
clientManager.findTiledWindow(client) === null,
|
||||
{ message: message, skip: skip+1 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ function init(config: Config) {
|
||||
|
||||
function getGridBounds(clientLeft: KwinClient, clientRight: KwinClient) {
|
||||
const columnsWidth = clientRight.frameGeometry.right - clientLeft.frameGeometry.left;
|
||||
const left = Math.floor((screen.width - columnsWidth) / 2);
|
||||
const left = tilingArea.left + Math.floor((tilingArea.width - columnsWidth) / 2);
|
||||
const right = left + columnsWidth;
|
||||
return { left, right };
|
||||
}
|
||||
@@ -44,3 +44,10 @@ function getWindowHeight(windowsInColumn: number) {
|
||||
const totalGaps = (windowsInColumn-1) * gapV;
|
||||
return Math.round((tilingArea.height - totalGaps) / windowsInColumn);
|
||||
}
|
||||
|
||||
function getClientManager(world: World): ClientManager {
|
||||
// don't do this outside of tests
|
||||
let clientManager;
|
||||
world.do((cm, dm) => clientManager = cm);
|
||||
return clientManager!;
|
||||
}
|
||||
|
||||
@@ -3,48 +3,46 @@ class MockKwinClient {
|
||||
|
||||
private static readonly borderThickness = 10;
|
||||
|
||||
public readonly shadeable: boolean = false;
|
||||
public readonly caption = "App";
|
||||
public caption = "App";
|
||||
public minSize: Readonly<QmlSize> = new MockQmlSize(0, 0);
|
||||
public readonly transient: boolean;
|
||||
public readonly move: boolean = false;
|
||||
public move: boolean = false;
|
||||
public resize: boolean = false;
|
||||
public readonly moveable: boolean = true;
|
||||
public readonly resizeable: boolean = true;
|
||||
public readonly fullScreenable: boolean = true;
|
||||
public readonly maximizable: boolean = true;
|
||||
public readonly output: Output = { __brand: "Output" };
|
||||
public readonly resourceClass = "app";
|
||||
public resourceClass = "app";
|
||||
public readonly dock: boolean = false;
|
||||
public readonly normalWindow: boolean = true;
|
||||
public readonly managed: boolean = true;
|
||||
public readonly popupWindow: boolean = false;
|
||||
public readonly pid = 1;
|
||||
|
||||
private _maximizedVertically: boolean = false;
|
||||
private _maximizedHorizontally: boolean = false;
|
||||
private _fullScreen: boolean = false;
|
||||
public activities: string[] = [];
|
||||
public skipSwitcher: boolean = false;
|
||||
public keepAbove: boolean = false;
|
||||
public keepBelow: boolean = false;
|
||||
public shade: boolean = false;
|
||||
public _minimized: boolean = false;
|
||||
public desktops: KwinDesktop[] = [];
|
||||
public _tile: Tile|null = null;
|
||||
private _minimized: boolean = false;
|
||||
private _desktops: KwinDesktop[] = [];
|
||||
private _tile: Tile|null = null;
|
||||
public opacity: number = 1.0;
|
||||
|
||||
public readonly fullScreenChanged = new MockQSignal();
|
||||
public readonly desktopsChanged = new MockQSignal();
|
||||
public readonly activitiesChanged = new MockQSignal();
|
||||
public readonly minimizedChanged = new MockQSignal();
|
||||
public readonly fullScreenChanged = new MockQSignal<[]>();
|
||||
public readonly desktopsChanged = new MockQSignal<[]>();
|
||||
public readonly activitiesChanged = new MockQSignal<[]>();
|
||||
public readonly minimizedChanged = new MockQSignal<[]>();
|
||||
public readonly maximizedAboutToChange = new MockQSignal<[MaximizedMode]>();
|
||||
public readonly captionChanged = new MockQSignal();
|
||||
public readonly tileChanged = new MockQSignal();
|
||||
public readonly interactiveMoveResizeStarted = new MockQSignal();
|
||||
public readonly interactiveMoveResizeFinished = new MockQSignal();
|
||||
public readonly captionChanged = new MockQSignal<[]>();
|
||||
public readonly tileChanged = new MockQSignal<[]>();
|
||||
public readonly interactiveMoveResizeStarted = new MockQSignal<[]>();
|
||||
public readonly interactiveMoveResizeFinished = new MockQSignal<[]>();
|
||||
public readonly frameGeometryChanged = new MockQSignal<[oldGeometry: QmlRect]>();
|
||||
|
||||
private windowedFrameGeometry: MockQmlRect;
|
||||
private windowed: boolean = false;
|
||||
private windowed: boolean = true;
|
||||
private hasBorder: boolean = true;
|
||||
|
||||
constructor(
|
||||
@@ -53,11 +51,18 @@ class MockKwinClient {
|
||||
) {
|
||||
this.windowedFrameGeometry = _frameGeometry.clone();
|
||||
this.transient = transientFor !== null;
|
||||
this._desktops = [Workspace.currentDesktop];
|
||||
}
|
||||
|
||||
setMaximize(vertically: boolean, horizontally: boolean) {
|
||||
this.windowed = !(vertically || horizontally);
|
||||
|
||||
if (vertically === this._maximizedVertically && horizontally === this._maximizedHorizontally) {
|
||||
return;
|
||||
}
|
||||
this._maximizedVertically = vertically;
|
||||
this._maximizedHorizontally = horizontally;
|
||||
|
||||
this.maximizedAboutToChange.fire(
|
||||
vertically ? (
|
||||
horizontally ? MaximizedMode.Maximized : MaximizedMode.Vertically
|
||||
@@ -87,6 +92,14 @@ class MockKwinClient {
|
||||
}
|
||||
}
|
||||
|
||||
public get moveable() {
|
||||
return !this._fullScreen;
|
||||
}
|
||||
|
||||
public get resizeable() {
|
||||
return !this._fullScreen;
|
||||
}
|
||||
|
||||
public get fullScreen() {
|
||||
return this._fullScreen;
|
||||
}
|
||||
@@ -167,6 +180,18 @@ class MockKwinClient {
|
||||
this.minimizedChanged.fire();
|
||||
}
|
||||
|
||||
public get desktops() {
|
||||
return this._desktops;
|
||||
}
|
||||
|
||||
public set desktops(desktops: KwinDesktop[]) {
|
||||
this._desktops = desktops;
|
||||
this.desktopsChanged.fire();
|
||||
if (Workspace.activeWindow === this && !desktops.includes(Workspace.currentDesktop)) {
|
||||
Workspace.activeWindow = null;
|
||||
};
|
||||
}
|
||||
|
||||
public get tile() {
|
||||
return this._tile;
|
||||
}
|
||||
@@ -189,4 +214,8 @@ class MockKwinClient {
|
||||
public getFrameGeometryCopy() {
|
||||
return this._frameGeometry.clone();
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `MockKwinClient("${this.caption}")`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ class MockQmlTimer {
|
||||
public readonly __brand = "QmlObject";
|
||||
|
||||
public interval = 0;
|
||||
public readonly triggered = new MockQSignal();
|
||||
public readonly triggered = new MockQSignal<[]>();
|
||||
|
||||
public restart() {
|
||||
// no need to wait in tests, just fire immediately
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class MockShortcutHandler {
|
||||
public readonly __brand = "QmlObject";
|
||||
|
||||
public readonly activated: MockQSignal<[]> = new MockQSignal();
|
||||
public readonly activated: MockQSignal<[]> = new MockQSignal<[]>();
|
||||
|
||||
public destroy() {}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class MockWorkspace {
|
||||
public currentDesktop = this.desktops[0];
|
||||
public currentActivity = this.activities[0];
|
||||
public activeScreen: Output = { __brand: "Output" };
|
||||
public windows = [];
|
||||
public readonly windows: MockKwinClient[] = [];
|
||||
public cursorPos = new MockQmlPoint(0, 0);
|
||||
|
||||
private _activeWindow: KwinClient|null = null;
|
||||
@@ -30,6 +30,7 @@ class MockWorkspace {
|
||||
|
||||
public createWindows(...kwinClients: MockKwinClient[]) {
|
||||
for (const kwinClient of kwinClients) {
|
||||
this.windows.push(kwinClient);
|
||||
this.windowAdded.fire(kwinClient);
|
||||
this.activeWindow = kwinClient;
|
||||
}
|
||||
@@ -41,6 +42,7 @@ class MockWorkspace {
|
||||
|
||||
public createClientsWithFrames(...frames: MockQmlRect[]) {
|
||||
const clients = frames.map(rect => new MockKwinClient(rect));
|
||||
clients.forEach((client, index) => client.caption = `Client ${index}`);
|
||||
this.createWindows(...clients);
|
||||
return clients;
|
||||
}
|
||||
@@ -49,6 +51,39 @@ class MockWorkspace {
|
||||
return this.createClientsWithFrames(...widths.map(width => new MockQmlRect(randomInt(100), randomInt(100), width, 100+randomInt(400))));
|
||||
}
|
||||
|
||||
public removeWindow(window: MockKwinClient) {
|
||||
runReorder(
|
||||
() => this.windows.splice(this.windows.indexOf(window), 1),
|
||||
() => this.windowRemoved.fire(window),
|
||||
);
|
||||
if (window === this.activeWindow) {
|
||||
const windows = this.windows.filter(w => w.desktops.includes(this.currentDesktop));
|
||||
Workspace.activeWindow = windows.length > 0 ? randomItem(windows) : null;
|
||||
};
|
||||
}
|
||||
|
||||
public moveWindow(window: MockKwinClient, ...deltas: QmlPoint[]) {
|
||||
const frame = window.getFrameGeometryCopy();
|
||||
window.move = true;
|
||||
window.interactiveMoveResizeStarted.fire();
|
||||
|
||||
for (const delta of deltas) {
|
||||
if (delta.x !== 0) {
|
||||
frame.x += delta.x;
|
||||
}
|
||||
if (delta.y !== 0) {
|
||||
frame.y += delta.y;
|
||||
}
|
||||
runOneOf(
|
||||
() => window.frameGeometry.set(frame),
|
||||
() => window.frameGeometry = frame,
|
||||
);
|
||||
}
|
||||
|
||||
window.move = false;
|
||||
window.interactiveMoveResizeFinished.fire();
|
||||
}
|
||||
|
||||
public resizeWindow(window: MockKwinClient, edgeResize: boolean, leftEdge: boolean, topEdge: boolean, ...deltas: QmlSize[]) {
|
||||
const frame = window.getFrameGeometryCopy();
|
||||
if (edgeResize) {
|
||||
@@ -81,7 +116,7 @@ class MockWorkspace {
|
||||
runOneOf(
|
||||
() => window.frameGeometry.set(frame),
|
||||
() => window.frameGeometry = frame,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
window.resize = false;
|
||||
|
||||
@@ -26,6 +26,12 @@ function randomInt(n: number) {
|
||||
return Math.floor(Math.random() * n);
|
||||
}
|
||||
|
||||
function randomItem(items: any[]) {
|
||||
Assert.assert(items.length > 0);
|
||||
const index = randomInt(items.length);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
function shuffle(items: any[]) {
|
||||
for (let n = items.length; n > 1; n--) {
|
||||
const i = n-1;
|
||||
|
||||
Reference in New Issue
Block a user