77 Commits
v0.5 ... v0.7

Author SHA1 Message Date
Peter Fajdiga
ad6c3f1cae bump version to 0.7 2024-02-11 20:37:26 +01:00
Peter Fajdiga
ba4dd2a9c1 config.ui: relabel scrollingGrouped button 2024-02-11 20:37:25 +01:00
Peter Fajdiga
bb61853009 config.ui: reorder tabs 2024-02-11 20:37:22 +01:00
Peter Fajdiga
0cfd9b9e36 Desktop: scrollCenterRange: add parameter prioritiseVisible 2024-01-22 08:55:05 +01:00
Peter Fajdiga
43c4f7ef9a Actions: columnWidthIncrease: add steps for fully visible screen-edge columns 2024-01-22 08:55:05 +01:00
Peter Fajdiga
9cb3f33ecb Actions: extract function findNextStep 2024-01-22 08:55:05 +01:00
Peter Fajdiga
31b9e61ae3 config.ui: add manualResizeStep 2024-01-22 08:55:05 +01:00
Peter Fajdiga
668e6696ab Actions: column width increase/decrease: replace screen-relative steps with column-relative steps 2024-01-22 08:55:05 +01:00
Peter Fajdiga
e63959cfbf Actions: getWidthSteps: ignore screen-relative steps too close to existing steps 2024-01-22 08:55:05 +01:00
Peter Fajdiga
ef2650beb8 Actions: improve snapping in columnWidthDecrease 2024-01-22 08:55:05 +01:00
Peter Fajdiga
750c47c040 implement resize steps (resolves #25) 2024-01-22 08:55:05 +01:00
Peter Fajdiga
88ca0d02e1 generators/config/kcfg.ts: escape xml characters in default config values 2024-01-21 19:40:12 +01:00
Peter Fajdiga
aba786b754 Desktop: rename scrollIntoView 2024-01-21 18:39:53 +01:00
Peter Fajdiga
47aa625c99 Actions: column width increase/decrease: use getCurrentVisibleRange 2024-01-21 18:39:25 +01:00
Peter Fajdiga
03c7cc6503 Desktop.scrollToRange: simplify 2024-01-21 18:37:34 +01:00
Peter Fajdiga
9e9ff2b74f remove overscroll feature (resolves #23) 2024-01-21 18:26:18 +01:00
Peter Fajdiga
5674624e6f Desktop.equalizeVisibleColumnsWidths: simplify scroll at the end 2024-01-21 18:17:29 +01:00
Peter Fajdiga
44dd88ef7c Desktop.equalizeVisibleColumnsWidths: handle columns with limited min width 2024-01-21 18:17:29 +01:00
Peter Fajdiga
f800d6ecf0 Desktop: rewrite ColumnRange.addNeighbors 2024-01-21 18:17:29 +01:00
Peter Fajdiga
3477e17bb3 Desktop: scrollCenterRange: replace parameter requireVisible with condition 2024-01-21 18:17:29 +01:00
Peter Fajdiga
755c781646 rename parameters of doIfTiled passed functions 2024-01-21 18:17:29 +01:00
Peter Fajdiga
926345ba31 move column width increase/decrease code to Actions.ts 2024-01-21 18:17:29 +01:00
Peter Fajdiga
a2295ede43 Desktop: add method scrollCenterVisible (moved from ScrollerGrouped) 2024-01-21 18:17:29 +01:00
Peter Fajdiga
ca80a7ca28 Grid.onColumnWidthChanged: fix autoAdjustScroll call 2024-01-14 15:40:24 +01:00
Peter Fajdiga
64474b1677 readme: mention Niri 2024-01-14 09:55:57 +01:00
Peter Fajdiga
eca63cbc16 readme: update key bindings 2023-12-30 17:26:59 +01:00
Peter Fajdiga
3a8baf4cd7 bump version to 0.6 2023-12-30 17:18:44 +01:00
Peter Fajdiga
dc14171ae7 config: add Wayland window classes to window rules (resolves #24) 2023-12-27 19:45:07 +01:00
Peter Fajdiga
6dcf8979c2 config: add yakuake to window rules 2023-12-27 19:41:30 +01:00
Peter Fajdiga
fe5661c07f Grid: auto-scroll after removing a non-focused column 2023-12-24 14:58:44 +01:00
Peter Fajdiga
90b783b34b Grid: remove unused methods getLeftOffScreenColumn and getRightOffScreenColumn 2023-12-24 12:52:10 +01:00
Peter Fajdiga
fb40bd9592 Grid.decreaseColumnWidth: prevent scrolling away from focused column when reaching minimum width 2023-12-24 12:50:52 +01:00
Peter Fajdiga
768d95450d refactor arrange functions 2023-12-24 09:29:31 +01:00
Peter Fajdiga
e95a0e44c9 prevent translucent windows during resizing 2023-12-24 09:21:49 +01:00
Peter Fajdiga
c1b8d05919 defaultWindowRules: update Zoom entry 2023-12-24 09:21:44 +01:00
Peter Fajdiga
e98ce18105 add opacity settings for obscured windows 2023-12-16 12:10:26 +01:00
Peter Fajdiga
25a9efc8e4 config.ui: split up parameters and behavior tabs 2023-12-16 11:39:42 +01:00
Peter Fajdiga
db48644944 package/contents/ui: add footer spacer to take up excess space 2023-12-16 11:39:42 +01:00
Peter Fajdiga
950e0de076 Desktop: prevent scrolling when unnecessary (add dirtyScroll variable) 2023-12-16 10:07:02 +01:00
Peter Fajdiga
05ffe0895e Revert "Desktop.scrollToCenterRange: force scroll"
This reverts commit dda63d68cde58c7f4a7162b11a2fd614365d36ff.
2023-12-16 09:33:26 +01:00
Peter Fajdiga
61db5ca69f use different implementations of clampScrollX in different scrollers 2023-12-16 09:33:26 +01:00
Peter Fajdiga
f7b5dd0b9c Desktop.equalizeVisibleColumnsWidths: use Desktop.RangeImpl.fromRanges 2023-12-16 09:33:26 +01:00
Peter Fajdiga
f83f60c98f Grid: reimplement increaseColumnWidth and decreaseColumnWidth 2023-12-16 09:33:26 +01:00
Peter Fajdiga
3e8734eefb Desktop.scrollToCenterRange: force scroll 2023-12-16 09:33:26 +01:00
Peter Fajdiga
bed0ea7ed8 Actions: make gridScrollFocused center focused column again 2023-12-16 09:33:26 +01:00
Peter Fajdiga
92c99f0b87 rename focusColumn -> scrollToColumn 2023-12-16 09:33:26 +01:00
Peter Fajdiga
b2024bc8aa Actions: make scrolling actions use focusColumn 2023-12-16 09:33:26 +01:00
Peter Fajdiga
58f358313b config.ui: change description for kcfg_scrollingLazy 2023-12-16 09:33:26 +01:00
Peter Fajdiga
352a7061f6 config.ui: shorten description for kcfg_scrollingCentered 2023-12-16 09:33:26 +01:00
Peter Fajdiga
7314c0ee24 add ScrollerGrouped 2023-12-16 09:33:26 +01:00
Peter Fajdiga
c65361853c config.ui: use button groups 2023-12-16 09:32:26 +01:00
Peter Fajdiga
fa53e765b3 qt.d.ts: rename QmlTimer 2023-12-16 09:30:19 +01:00
Peter Fajdiga
4d35681ee2 qt.d.ts: rename QmlSize 2023-12-16 09:30:14 +01:00
Peter Fajdiga
1824bcdf85 fix usages of QmlRect.right and .bottom 2023-12-12 17:33:10 +01:00
Peter Fajdiga
ce1b402bf2 qt.d.ts: add comments to QmlRect fields 2023-12-10 21:08:00 +01:00
Peter Fajdiga
2df6d5d8e6 qt.d.ts: rename QmlRect 2023-12-10 21:06:03 +01:00
Peter Fajdiga
e7d33030ba read and use scrolling configuration 2023-12-05 22:15:49 +01:00
Peter Fajdiga
2bd000f0a6 config.ui: add scrolling configuration 2023-12-05 22:03:22 +01:00
Peter Fajdiga
6313d8f18e implement scrollers 2023-12-05 21:59:01 +01:00
Peter Fajdiga
464ec3bcb1 Grid.increaseColumnWidth: fix scroll adjustment after resize 2023-12-02 20:57:46 +01:00
Peter Fajdiga
fae793cb09 Desktop.equalizeVisibleColumnsWidths: adjust scroll after resizing 2023-12-02 20:50:13 +01:00
Peter Fajdiga
d7346a6fab Desktop.clampScrollX: simplify 2023-12-02 20:38:14 +01:00
Peter Fajdiga
0e5efd2be7 Desktop: receive Range in parameters instead of Column 2023-12-02 17:36:29 +01:00
Peter Fajdiga
a1a315790e turn Desktop.Range into an interface (well, into a type) 2023-12-02 14:12:41 +01:00
Peter Fajdiga
9d62499bf0 Grid.Range: make fields private 2023-12-02 14:09:23 +01:00
Peter Fajdiga
97cf61d1dd Grid: extract method calculateVisibleRange 2023-12-02 14:06:28 +01:00
Peter Fajdiga
5d83c6dd2c rename Grid.ScrollPos to Grid.Range 2023-12-02 13:55:39 +01:00
Peter Fajdiga
8915e8a9da Grid.getVisibleColumnsWidth: use getVisibleColumns 2023-12-02 12:44:57 +01:00
Peter Fajdiga
22e4c47189 add action columns-width-equalize (resolves #22) 2023-12-02 12:44:57 +01:00
Peter Fajdiga
552d2b851f readme: list QML dependencies 2023-11-12 10:02:33 +01:00
Peter Fajdiga
c4ce795359 config.ui: add stacked mode description to tooltip 2023-09-29 10:23:33 +02:00
Peter Fajdiga
1ac1fc3c2b readme: update key bindings 2023-09-29 10:21:47 +02:00
Peter Fajdiga
d0e041d16a keyBindings: add comment for column-toggle-stacked 2023-09-29 10:21:26 +02:00
Peter Fajdiga
8fc3fc976d Window: WindowState -> Window.State 2023-09-29 08:58:38 +02:00
Peter Fajdiga
e0eeace9dc ClientState.Manager: ; -> , 2023-09-29 08:58:37 +02:00
Peter Fajdiga
84e2a06b35 Tiled: define type WindowState 2023-09-29 08:56:45 +02:00
Peter Fajdiga
3373e02658 add skipSwitcher setting 2023-09-29 08:52:59 +02:00
27 changed files with 707 additions and 365 deletions

View File

@@ -13,9 +13,16 @@ unprompted reflow of window content.
Windows are automatically centered when possible. And when running out of width, windows can be Windows are automatically centered when possible. And when running out of width, windows can be
scrolled through horizontally. scrolled through horizontally.
Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM) and Similar window managers include [PaperWM](https://github.com/paperwm/PaperWM),
[Niri](https://github.com/YaLTeR/niri), and
[Cardboard](https://gitlab.com/cardboardwm/cardboard). [Cardboard](https://gitlab.com/cardboardwm/cardboard).
## Dependencies
Karousel requires the following QML modules:
- QtQuick 2.15
- org.kde.kwin 3.0
- org.kde.notification 1.0
## Limitations ## Limitations
- Doesn't support multiple screens - Doesn't support multiple screens
- Doesn't support windows on all desktops - Doesn't support windows on all desktops
@@ -39,13 +46,14 @@ Here's the default ones:
| Meta+Shift+S | Move window down | | Meta+Shift+S | Move window down |
| Meta+Shift+Home | Move window to start | | Meta+Shift+Home | Move window to start |
| Meta+Shift+End | Move window to end | | Meta+Shift+End | Move window to end |
| Meta+X | Toggle stacked layout for focused column | | Meta+X | Toggle stacked layout for focused column (One window in the column visible, others shaded; not supported on Wayland) |
| Meta+Ctrl+Shift+A | Move column left | | Meta+Ctrl+Shift+A | Move column left |
| Meta+Ctrl+Shift+D | Move column right | | Meta+Ctrl+Shift+D | Move column right |
| Meta+Ctrl+Shift+Home | Move column to start | | Meta+Ctrl+Shift+Home | Move column to start |
| Meta+Ctrl+Shift+End | Move column to end | | Meta+Ctrl+Shift+End | Move column to end |
| Meta+Ctrl++ | Increase column width | | Meta+Ctrl++ | Increase column width |
| Meta+Ctrl+- | Decrease column width | | Meta+Ctrl+- | Decrease column width |
| Meta+Ctrl+X | Equalize widths of visible columns |
| Meta+Alt+Return | Center focused window (Scrolls so that the focused window is centered in the screen) | | Meta+Alt+Return | Center focused window (Scrolls so that the focused window is centered in the screen) |
| Meta+Alt+A | Scroll one column to the left | | Meta+Alt+A | Scroll one column to the left |
| Meta+Alt+D | Scroll one column to the right | | Meta+Alt+D | Scroll one column to the right |

View File

@@ -5,9 +5,20 @@ console.log(`<?xml version="1.0" encoding="UTF-8"?>
for (const entry of configDef) { for (const entry of configDef) {
console.log(` <entry name="${entry.name}" type="${entry.type}"> console.log(` <entry name="${entry.name}" type="${entry.type}">
<default>${entry.default}</default> <default>${escapeXml(entry.default)}</default>
</entry>`); </entry>`);
} }
console.log(` </group> console.log(` </group>
</kcfg>`); </kcfg>`);
function escapeXml(input: any) {
if (typeof input === "string") {
return input
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
} else {
return input;
}
}

View File

@@ -11,12 +11,141 @@
<layout class="QVBoxLayout" name="layout_main"> <layout class="QVBoxLayout" name="layout_main">
<item> <item>
<widget class="QTabWidget" name="tabContainer"> <widget class="QTabWidget" name="tabContainer">
<widget class="QWidget" name="tab_general"> <widget class="QWidget" name="tab_behavior">
<attribute name="title"> <attribute name="title">
<string>General</string> <string>Behavior</string>
</attribute> </attribute>
<layout class="QFormLayout" name="layout_tab_general"> <layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="kcfg_untileOnDrag">
<property name="text">
<string>Un-tile windows by dragging them</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
<property name="text">
<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>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_resizeNeighborColumn">
<property name="text">
<string>Resize neighbor column on edge resize</string>
</property>
<property name="toolTip">
<string>When resizing a column by dragging its edge, also inversely resize the column on the other side of the edge</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_reMaximize">
<property name="text">
<string>Re-maximize tiled windows</string>
</property>
<property name="toolTip">
<string>Restore maximized and full-screen states of tiled windows on focus</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_skipSwitcher">
<property name="text">
<string>Tiled windows skip switcher</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Scrolling mode</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="kcfg_scrollingLazy">
<property name="text">
<string>Only scroll as necessary</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_scrollingCentered">
<property name="text">
<string>Center focused column</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_scrollingGrouped">
<property name="text">
<string>Center visible columns</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Layering mode</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="kcfg_tiledKeepBelow">
<property name="text">
<string>Keep tiled windows below</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="kcfg_floatingKeepAbove">
<property name="text">
<string>Keep floating windows above</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="spacer_footer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_parameters">
<attribute name="title">
<string>Parameters</string>
</attribute>
<layout class="QFormLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_gapsOuterTop"> <widget class="QLabel" name="label_gapsOuterTop">
<property name="text"> <property name="text">
@@ -144,34 +273,13 @@
</item> </item>
<item row="6" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_overscroll">
<property name="text">
<string>Overscroll amount:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="kcfg_overscroll">
<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="7" column="0">
<widget class="QLabel" name="label_manualScrollStep"> <widget class="QLabel" name="label_manualScrollStep">
<property name="text"> <property name="text">
<string>Manual scroll step size:</string> <string>Manual scroll step size:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="6" column="1">
<widget class="QSpinBox" name="kcfg_manualScrollStep"> <widget class="QSpinBox" name="kcfg_manualScrollStep">
<property name="suffix"> <property name="suffix">
<string> px</string> <string> px</string>
@@ -185,112 +293,55 @@
</widget> </widget>
</item> </item>
<item row="7" column="0">
<widget class="QLabel" name="label_manualResizeStep">
<property name="text">
<string>Manual resize step size:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="kcfg_manualResizeStep">
<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_offScreenOpacity">
<property name="text">
<string>Obscured window opacity:</string>
</property>
</widget>
</item>
<item row="8" column="1"> <item row="8" column="1">
<spacer name="separator_behavior"> <widget class="QSpinBox" name="kcfg_offScreenOpacity">
<property name="orientation"> <property name="suffix">
<enum>Qt::Vertical</enum> <string> %</string>
</property> </property>
<property name="sizeType"> <property name="maximum">
<enum>QSizePolicy::Fixed</enum> <number>100</number>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="value">
<size> <number>0</number>
<width>0</width>
<height>12</height>
</size>
</property>
</spacer>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_behavior">
<property name="text">
<string>Behavior:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1">
<widget class="QCheckBox" name="kcfg_untileOnDrag">
<property name="text">
<string>Un-tile windows by dragging them</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QCheckBox" name="kcfg_stackColumnsByDefault">
<property name="text">
<string>Stack columns by default</string>
</property>
<property name="toolTip">
<string>New columns start in stacked mode</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QCheckBox" name="kcfg_resizeNeighborColumn">
<property name="text">
<string>Resize neighbor column on edge resize</string>
</property>
<property name="toolTip">
<string>When resizing a column by dragging its edge, also inversely resize the column on the other side of the edge</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QCheckBox" name="kcfg_reMaximize">
<property name="text">
<string>Re-maximize tiled windows</string>
</property>
<property name="toolTip">
<string>Restore maximized and full-screen states of tiled windows on focus</string>
</property>
</widget>
</item>
<item row="13" column="1">
<spacer name="separator_layering">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>12</height>
</size>
</property>
</spacer>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_layering">
<property name="text">
<string>Layering mode:</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QRadioButton" name="kcfg_tiledKeepBelow">
<property name="text">
<string>Keep tiled windows below</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QRadioButton" name="kcfg_floatingKeepAbove">
<property name="text">
<string>Keep floating windows above</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_windowRules"> <widget class="QWidget" name="tab_windowRules">
<attribute name="title"> <attribute name="title">
<string>Window Rules</string> <string>Window Rules</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="layout_tab_windowRules"> <layout class="QVBoxLayout">
<item> <item>
<widget class="QPlainTextEdit" name="kcfg_windowRules"> <widget class="QPlainTextEdit" name="kcfg_windowRules">
<property name="tabChangesFocus"> <property name="tabChangesFocus">

View File

@@ -9,7 +9,7 @@
}], }],
"Id": "karousel", "Id": "karousel",
"ServiceTypes": ["KWin/Script"], "ServiceTypes": ["KWin/Script"],
"Version": "0.5", "Version": "0.7",
"License": "GPLv3", "License": "GPLv3",
"Website": "https://github.com/peterfajdiga/karousel", "Website": "https://github.com/peterfajdiga/karousel",
"BugReportUrl": "https://github.com/peterfajdiga/karousel/issues" "BugReportUrl": "https://github.com/peterfajdiga/karousel/issues"

View File

@@ -2,7 +2,7 @@ namespace Actions {
export function init(world: World, config: Config) { export function init(world: World, config: Config) {
return { return {
focusLeft: () => { focusLeft: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const prevColumn = grid.getPrevColumn(column); const prevColumn = grid.getPrevColumn(column);
if (prevColumn === null) { if (prevColumn === null) {
return; return;
@@ -12,7 +12,7 @@ namespace Actions {
}, },
focusRight: () => { focusRight: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const nextColumn = grid.getNextColumn(column); const nextColumn = grid.getNextColumn(column);
if (nextColumn === null) { if (nextColumn === null) {
return; return;
@@ -22,7 +22,7 @@ namespace Actions {
}, },
focusUp: () => { focusUp: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const prevWindow = column.getPrevWindow(window); const prevWindow = column.getPrevWindow(window);
if (prevWindow === null) { if (prevWindow === null) {
return; return;
@@ -32,7 +32,7 @@ namespace Actions {
}, },
focusDown: () => { focusDown: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const nextWindow = column.getNextWindow(window); const nextWindow = column.getNextWindow(window);
if (nextWindow === null) { if (nextWindow === null) {
return; return;
@@ -64,7 +64,7 @@ namespace Actions {
}, },
windowMoveLeft: () => { windowMoveLeft: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
if (column.getWindowCount() === 1) { if (column.getWindowCount() === 1) {
// move from own column into existing column // move from own column into existing column
const prevColumn = grid.getPrevColumn(column); const prevColumn = grid.getPrevColumn(column);
@@ -82,7 +82,7 @@ namespace Actions {
}, },
windowMoveRight: () => { windowMoveRight: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
if (column.getWindowCount() === 1) { if (column.getWindowCount() === 1) {
// move from own column into existing column // move from own column into existing column
const nextColumn = grid.getNextColumn(column); const nextColumn = grid.getNextColumn(column);
@@ -101,27 +101,27 @@ namespace Actions {
windowMoveUp: () => { windowMoveUp: () => {
// TODO (optimization): only arrange moved windows // TODO (optimization): only arrange moved windows
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveWindowUp(window); column.moveWindowUp(window);
}); });
}, },
windowMoveDown: () => { windowMoveDown: () => {
// TODO (optimization): only arrange moved windows // TODO (optimization): only arrange moved windows
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveWindowDown(window); column.moveWindowDown(window);
}); });
}, },
windowMoveStart: () => { windowMoveStart: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const newColumn = new Column(grid, null); const newColumn = new Column(grid, null);
window.moveToColumn(newColumn); window.moveToColumn(newColumn);
}); });
}, },
windowMoveEnd: () => { windowMoveEnd: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const newColumn = new Column(grid, grid.getLastColumn()); const newColumn = new Column(grid, grid.getLastColumn());
window.moveToColumn(newColumn); window.moveToColumn(newColumn);
}); });
@@ -135,44 +135,126 @@ namespace Actions {
}, },
columnMoveLeft: () => { columnMoveLeft: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
grid.moveColumnLeft(column); grid.moveColumnLeft(column);
}); });
}, },
columnMoveRight: () => { columnMoveRight: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
grid.moveColumnRight(column); grid.moveColumnRight(column);
}); });
}, },
columnMoveStart: () => { columnMoveStart: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveAfter(null); column.moveAfter(null);
}); });
}, },
columnMoveEnd: () => { columnMoveEnd: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
column.moveAfter(grid.getLastColumn()); column.moveAfter(grid.getLastColumn());
}); });
}, },
columnToggleStacked: () => { columnToggleStacked: () => {
world.doIfTiledFocused(false, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
column.toggleStacked(); column.toggleStacked();
}); });
}, },
columnWidthIncrease: () => { columnWidthIncrease: () => {
world.doIfTiledFocused(false, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
grid.increaseColumnWidth(column); const desktop = grid.desktop;
const visibleRange = desktop.getCurrentVisibleRange();
if(!column.isVisible(visibleRange, true) || column.getWidth() >= column.getMaxWidth()) {
return;
}
let leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange, true);
let rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange, true);
if (leftVisibleColumn === null || rightVisibleColumn === null) {
console.assert(false); // should at least see self
return;
}
const leftSpace = leftVisibleColumn.getLeft() - visibleRange.getLeft();
const rightSpace = visibleRange.getRight() - rightVisibleColumn.getRight();
const newWidth = findNextStep(
[
visibleRange.getWidth(),
column.getWidth() + config.manualResizeStep,
column.getWidth() + leftSpace + rightSpace,
column.getWidth() + leftSpace + rightSpace + leftVisibleColumn.getWidth() + grid.config.gapsInnerHorizontal,
column.getWidth() + leftSpace + rightSpace + rightVisibleColumn.getWidth() + grid.config.gapsInnerHorizontal,
],
width => width - column.getWidth(),
)
if (newWidth === undefined) {
return;
}
column.setWidth(newWidth, true);
desktop.scrollCenterVisible(column, false);
desktop.onLayoutChanged();
desktop.autoAdjustScroll();
}); });
}, },
columnWidthDecrease: () => { columnWidthDecrease: () => {
world.doIfTiledFocused(false, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(false, (clientManager, desktopManager, window, column, grid) => {
grid.decreaseColumnWidth(column); const desktop = grid.desktop;
const visibleRange = desktop.getCurrentVisibleRange();
if(!column.isVisible(visibleRange, true) || column.getWidth() <= column.getMinWidth()) {
return;
}
const leftVisibleColumn = grid.getLeftmostVisibleColumn(visibleRange, true);
const rightVisibleColumn = grid.getRightmostVisibleColumn(visibleRange, true);
if (leftVisibleColumn === null || rightVisibleColumn === null) {
console.assert(false); // should at least see self
return;
}
let leftOffScreenColumn = grid.getPrevColumn(leftVisibleColumn);
if (leftOffScreenColumn === column) {
leftOffScreenColumn = null;
}
let rightOffScreenColumn = grid.getNextColumn(rightVisibleColumn);
if (rightOffScreenColumn === column) {
rightOffScreenColumn = null;
}
const visibleColumnsWidth = rightVisibleColumn.getRight() - leftVisibleColumn.getLeft();
const unusedWidth = visibleRange.getWidth() - visibleColumnsWidth;
const leftOffScreen = leftOffScreenColumn === null ? 0 : leftOffScreenColumn.getWidth() + grid.config.gapsInnerHorizontal - unusedWidth;
const rightOffScreen = rightOffScreenColumn === null ? 0 : rightOffScreenColumn.getWidth() + grid.config.gapsInnerHorizontal - unusedWidth;
const newWidth = findNextStep(
[
visibleRange.getWidth(),
column.getWidth() - config.manualResizeStep,
column.getWidth() - leftOffScreen,
column.getWidth() - rightOffScreen,
],
width => column.getWidth() - width,
)
if (newWidth === undefined) {
return;
}
column.setWidth(newWidth, true);
desktop.scrollCenterVisible(column, true);
desktop.onLayoutChanged();
desktop.autoAdjustScroll();
});
},
columnsWidthEqualize: () => {
world.do((clientManager, desktopManager) => {
desktopManager.getCurrentDesktop().equalizeVisibleColumnsWidths();
}); });
}, },
@@ -207,15 +289,15 @@ namespace Actions {
}, },
gridScrollFocused: () => { gridScrollFocused: () => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
grid.desktop.scrollCenterColumn(column); grid.desktop.scrollCenterRange(column);
}) })
}, },
gridScrollLeftColumn: () => { gridScrollLeftColumn: () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentScrollPos(), true); const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
if (column === null) { if (column === null) {
return; return;
} }
@@ -232,7 +314,7 @@ namespace Actions {
gridScrollRightColumn: () => { gridScrollRightColumn: () => {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
const grid = desktopManager.getCurrentDesktop().grid; const grid = desktopManager.getCurrentDesktop().grid;
const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentScrollPos(), true); const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
if (column === null) { if (column === null) {
return; return;
} }
@@ -262,7 +344,7 @@ namespace Actions {
}, },
windowMoveToColumn: (columnIndex: number) => { windowMoveToColumn: (columnIndex: number) => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const targetColumn = grid.getColumnAtIndex(columnIndex); const targetColumn = grid.getColumnAtIndex(columnIndex);
if (targetColumn === null) { if (targetColumn === null) {
return null; return null;
@@ -273,7 +355,7 @@ namespace Actions {
}, },
columnMoveToColumn: (columnIndex: number) => { columnMoveToColumn: (columnIndex: number) => {
world.doIfTiledFocused(true, (world, desktopManager, window, column, grid) => { world.doIfTiledFocused(true, (clientManager, desktopManager, window, column, grid) => {
const targetColumn = grid.getColumnAtIndex(columnIndex); const targetColumn = grid.getColumnAtIndex(columnIndex);
if (targetColumn === null || targetColumn === column) { if (targetColumn === null || targetColumn === column) {
return null; return null;
@@ -317,7 +399,21 @@ namespace Actions {
}); });
} }
function findNextStep(steps: number[], evaluate: (step: number) => number) {
let bestScore = Infinity;
let bestStep = undefined;
for (const step of steps) {
const score = evaluate(step);
if (score > 0 && score < bestScore) {
bestScore = score;
bestStep = step;
}
}
return bestStep;
}
export type Config = { export type Config = {
manualScrollStep: number, manualScrollStep: number,
manualResizeStep: number,
}; };
} }

View File

@@ -5,12 +5,17 @@ type Config = {
gapsOuterRight: number, gapsOuterRight: number,
gapsInnerHorizontal: number, gapsInnerHorizontal: number,
gapsInnerVertical: number, gapsInnerVertical: number,
overscroll: number,
manualScrollStep: number, manualScrollStep: number,
manualResizeStep: number,
offScreenOpacity: number,
untileOnDrag: boolean, untileOnDrag: boolean,
stackColumnsByDefault: boolean, stackColumnsByDefault: boolean,
resizeNeighborColumn: boolean, resizeNeighborColumn: boolean,
reMaximize: boolean, reMaximize: boolean,
skipSwitcher: boolean,
scrollingLazy: boolean,
scrollingCentered: boolean,
scrollingGrouped: boolean,
tiledKeepBelow: boolean, tiledKeepBelow: boolean,
floatingKeepAbove: boolean, floatingKeepAbove: boolean,
windowRules: string, windowRules: string,

View File

@@ -7,26 +7,45 @@ const defaultWindowRules = `[
"class": "kcalc", "class": "kcalc",
"tile": false "tile": false
}, },
{
"class": "org.kde.kcalc",
"tile": false
},
{ {
"class": "kfind", "class": "kfind",
"tile": true "tile": true
}, },
{
"class": "org.kde.kfind",
"tile": true
},
{ {
"class": "kruler", "class": "kruler",
"tile": false "tile": false
}, },
{
"class": "org.kde.kruler",
"tile": false
},
{ {
"class": "krunner", "class": "krunner",
"tile": false "tile": false
}, },
{ {
"class": "zoom", "class": "org.kde.krunner",
"caption": "Zoom Cloud Meetings", "tile": false
},
{
"class": "yakuake",
"tile": false
},
{
"class": "org.kde.yakuake",
"tile": false "tile": false
}, },
{ {
"class": "zoom", "class": "zoom",
"caption": "zoom", "caption": "Zoom Cloud Meetings|zoom|zoom <2>",
"tile": false "tile": false
}, },
{ {
@@ -82,16 +101,21 @@ const configDef = [
"type": "UInt", "type": "UInt",
"default": 18 "default": 18
}, },
{
"name": "overscroll",
"type": "UInt",
"default": 0
},
{ {
"name": "manualScrollStep", "name": "manualScrollStep",
"type": "UInt", "type": "UInt",
"default": 200 "default": 200
}, },
{
"name": "manualResizeStep",
"type": "UInt",
"default": 600
},
{
"name": "offScreenOpacity",
"type": "UInt",
"default": 100
},
{ {
"name": "untileOnDrag", "name": "untileOnDrag",
"type": "Bool", "type": "Bool",
@@ -112,6 +136,26 @@ const configDef = [
"type": "Bool", "type": "Bool",
"default": false "default": false
}, },
{
"name": "skipSwitcher",
"type": "Bool",
"default": false
},
{
"name": "scrollingLazy",
"type": "Bool",
"default": true
},
{
"name": "scrollingCentered",
"type": "Bool",
"default": false
},
{
"name": "scrollingGrouped",
"type": "Bool",
"default": false
},
{ {
"name": "tiledKeepBelow", "name": "tiledKeepBelow",
"type": "Bool", "type": "Bool",

View File

@@ -36,7 +36,7 @@ interface KwinClient {
// Read-only Properties // Read-only Properties
readonly shadeable: boolean; readonly shadeable: boolean;
readonly caption: string; readonly caption: string;
readonly minSize: QSize; readonly minSize: QmlSize;
readonly transient: boolean; readonly transient: boolean;
readonly transientFor: KwinClient; readonly transientFor: KwinClient;
readonly move: boolean; readonly move: boolean;
@@ -47,15 +47,17 @@ interface KwinClient {
readonly dock: boolean; readonly dock: boolean;
readonly normalWindow: boolean; readonly normalWindow: boolean;
readonly managed: boolean; readonly managed: boolean;
opacity: number;
// Read-write Properties // Read-write Properties
fullScreen: boolean; fullScreen: boolean;
activities: string[]; // empty array means all activities activities: string[]; // empty array means all activities
skipSwitcher: boolean;
keepAbove: boolean; keepAbove: boolean;
keepBelow: boolean; keepBelow: boolean;
shade: boolean; shade: boolean;
minimized: boolean; minimized: boolean;
frameGeometry: QRect; frameGeometry: QmlRect;
desktop: number; // -1 means all desktops desktop: number; // -1 means all desktops
tile: Tile; tile: Tile;
@@ -68,7 +70,7 @@ interface KwinClient {
moveResizedChanged: QSignal<[void]>; moveResizedChanged: QSignal<[void]>;
moveResizeCursorChanged: QSignal<[void]>; moveResizeCursorChanged: QSignal<[void]>;
clientStartUserMovedResized: QSignal<[void]>; clientStartUserMovedResized: QSignal<[void]>;
frameGeometryChanged: QSignal<[KwinClient, oldGeometry: QRect]>; frameGeometryChanged: QSignal<[KwinClient, oldGeometry: QmlRect]>;
// Functions // Functions
setMaximize(vertically: boolean, horizontally: boolean): void; setMaximize(vertically: boolean, horizontally: boolean): void;

12
src/extern/qt.d.ts vendored
View File

@@ -4,7 +4,7 @@ declare const console: {
}; };
declare const Qt: { declare const Qt: {
rect(x: number, y: number, width: number, height: number): QRect; rect(x: number, y: number, width: number, height: number): QmlRect;
createQmlObject(qml: string, parent: QmlObject); createQmlObject(qml: string, parent: QmlObject);
}; };
@@ -12,18 +12,18 @@ type QmlObject = unknown;
type QByteArray = string; type QByteArray = string;
type QRect = { type QmlRect = {
x: number; x: number;
y: number; y: number;
width: number; width: number;
height: number; height: number;
top: number; top: number;
bottom: number; bottom: number; // top + height
left: number; left: number;
right: number; right: number; // left + width
}; };
type QSize = { type QmlSize = {
width: number; width: number;
height: number; height: number;
}; };
@@ -33,7 +33,7 @@ type QSignal<T extends unknown[]> = {
disconnect(handler: (...args: [...T]) => void): void; disconnect(handler: (...args: [...T]) => void): void;
}; };
type QQmlTimer = { type QmlTimer = {
interval: number; interval: number;
triggered: QSignal<[void]>; triggered: QSignal<[void]>;
restart(): void; restart(): void;

View File

@@ -85,6 +85,7 @@ const keyBindings: KeyBinding[] = [
{ {
"name": "column-toggle-stacked", "name": "column-toggle-stacked",
"description": "Toggle stacked layout for focused column", "description": "Toggle stacked layout for focused column",
"comment": "One window in the column visible, others shaded; not supported on Wayland",
"defaultKeySequence": "Meta+X", "defaultKeySequence": "Meta+X",
"action": "columnToggleStacked", "action": "columnToggleStacked",
}, },
@@ -124,6 +125,12 @@ const keyBindings: KeyBinding[] = [
"defaultKeySequence": "Meta+Ctrl+-", "defaultKeySequence": "Meta+Ctrl+-",
"action": "columnWidthDecrease", "action": "columnWidthDecrease",
}, },
{
"name": "columns-width-equalize",
"description": "Equalize widths of visible columns",
"defaultKeySequence": "Meta+Ctrl+X",
"action": "columnsWidthEqualize",
},
{ {
"name": "grid-scroll-focused", "name": "grid-scroll-focused",
"description": "Center focused window", "description": "Center focused window",

View File

@@ -180,7 +180,14 @@ class Column {
window.focus(); window.focus();
} }
public arrange(x: number) { 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;
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.canStack()) {
this.arrangeStacked(x); this.arrangeStacked(x);
return; return;
@@ -237,13 +244,13 @@ class Column {
return true; return true;
} }
public isVisible(scrollPos: Desktop.ScrollPos, fullyVisible: boolean) { public isVisible(visibleRange: Desktop.Range, fullyVisible: boolean) {
if (fullyVisible) { if (fullyVisible) {
return this.getLeft() >= scrollPos.getLeft() && return this.getLeft() >= visibleRange.getLeft() &&
this.getRight() <= scrollPos.getRight(); this.getRight() <= visibleRange.getRight();
} else { } else {
return this.getRight() + this.grid.config.gapsInnerHorizontal > scrollPos.getLeft() && return this.getRight() + this.grid.config.gapsInnerHorizontal > visibleRange.getLeft() &&
this.getLeft() - this.grid.config.gapsInnerHorizontal < scrollPos.getRight(); this.getLeft() - this.grid.config.gapsInnerHorizontal < visibleRange.getRight();
} }
} }

View File

@@ -5,15 +5,17 @@ class Desktop {
private readonly config: Desktop.Config; private readonly config: Desktop.Config;
private scrollX: number; private scrollX: number;
private dirty: boolean; private dirty: boolean;
private dirtyScroll: boolean;
private dirtyPins: boolean; private dirtyPins: boolean;
public clientArea: QRect; public clientArea: QmlRect;
public tilingArea: QRect; public tilingArea: QmlRect;
constructor(desktopNumber: number, pinManager: PinManager, config: Desktop.Config, layoutConfig: LayoutConfig) { constructor(desktopNumber: number, pinManager: PinManager, config: Desktop.Config, layoutConfig: LayoutConfig) {
this.pinManager = pinManager; this.pinManager = pinManager;
this.config = config; this.config = config;
this.scrollX = 0; this.scrollX = 0;
this.dirty = true; this.dirty = true;
this.dirtyScroll = true;
this.dirtyPins = true; this.dirtyPins = true;
this.desktopNumber = desktopNumber; this.desktopNumber = desktopNumber;
this.grid = new Grid(this, layoutConfig); this.grid = new Grid(this, layoutConfig);
@@ -29,6 +31,7 @@ class Desktop {
this.clientArea = newClientArea; this.clientArea = newClientArea;
this.tilingArea = Desktop.getTilingArea(newClientArea, this.desktopNumber, this.pinManager, this.config); this.tilingArea = Desktop.getTilingArea(newClientArea, this.desktopNumber, this.pinManager, this.config);
this.dirty = true; this.dirty = true;
this.dirtyScroll = true;
this.dirtyPins = false; this.dirtyPins = false;
this.grid.onScreenSizeChanged(); this.grid.onScreenSizeChanged();
this.autoAdjustScroll(); this.autoAdjustScroll();
@@ -38,7 +41,7 @@ class Desktop {
return workspace.clientArea(ClientAreaOption.PlacementArea, 0, desktopNumber); return workspace.clientArea(ClientAreaOption.PlacementArea, 0, desktopNumber);
} }
private static getTilingArea(clientArea: QRect, desktopNumber: number, pinManager: PinManager, config: Desktop.Config) { private static getTilingArea(clientArea: QmlRect, desktopNumber: number, pinManager: PinManager, config: Desktop.Config) {
const availableSpace = pinManager.getAvailableSpace(desktopNumber, clientArea); const availableSpace = pinManager.getAvailableSpace(desktopNumber, clientArea);
const top = availableSpace.top + config.marginTop; const top = availableSpace.top + config.marginTop;
const bottom = availableSpace.bottom - config.marginBottom; const bottom = availableSpace.bottom - config.marginBottom;
@@ -47,102 +50,116 @@ class Desktop {
return Qt.rect( return Qt.rect(
left, left,
top, top,
right - left + 1, right - left,
bottom - top + 1, bottom - top,
) )
} }
// calculates Desktop.Pos that scrolls the column into view public scrollIntoView(range: Desktop.Range) {
public getScrollPosForColumn(column: Column) { const left = range.getLeft();
const left = column.getLeft(); const right = range.getRight();
const right = column.getRight(); const initialVisibleRange = this.getCurrentVisibleRange();
const initialScrollPos = this.getCurrentScrollPos();
let targetScrollX: number; let targetScrollX: number;
if (left < initialScrollPos.getLeft()) { if (left < initialVisibleRange.getLeft()) {
targetScrollX = left; targetScrollX = left;
} else if (right > initialScrollPos.getRight()) { } else if (right > initialVisibleRange.getRight()) {
targetScrollX = right - this.tilingArea.width; targetScrollX = right - this.tilingArea.width;
} else { } else {
return this.getScrollPos(this.clampScrollX(this.scrollX)); targetScrollX = initialVisibleRange.getLeft();
} }
const overscroll = this.getTargetOverscroll(targetScrollX, left < initialScrollPos.getLeft()); this.setScroll(targetScrollX, false);
return this.getScrollPos(this.clampScrollX(targetScrollX + overscroll));
} }
private getTargetOverscroll(targetScrollX: number, scrollLeft: boolean) { public scrollCenterRange(range: Desktop.Range) {
if (this.config.overscroll === 0) { const windowCenter = range.getLeft() + range.getWidth() / 2;
return 0;
}
const visibleColumnsWidth = this.grid.getVisibleColumnsWidth(this.getScrollPos(targetScrollX), true);
const remainingSpace = this.tilingArea.width - visibleColumnsWidth;
const overscrollX = Math.min(this.config.overscroll, Math.round(remainingSpace / 2));
const direction = scrollLeft ? -1 : 1;
return overscrollX * direction;
}
public scrollToColumn(column: Column) {
this.setScroll(this.getScrollPosForColumn(column).x, true);
}
public scrollCenterColumn(column: Column) {
const windowCenter = column.getLeft() + column.getWidth() / 2;
const screenCenter = this.scrollX + this.tilingArea.width / 2; const screenCenter = this.scrollX + this.tilingArea.width / 2;
this.adjustScroll(Math.round(windowCenter - screenCenter), false); this.adjustScroll(Math.round(windowCenter - screenCenter), false);
} }
public scrollCenterVisible(focusedColumn: Column, prioritiseVisible: boolean) {
const columnRange = new Desktop.ColumnRange(focusedColumn);
const visibleRange = this.getCurrentVisibleRange();
if (prioritiseVisible) {
columnRange.addNeighbors(visibleRange, this.grid.config.gapsInnerHorizontal, column => column.isVisible(visibleRange, true));
}
columnRange.addNeighbors(visibleRange, this.grid.config.gapsInnerHorizontal, () => true);
this.scrollCenterRange(columnRange);
}
public autoAdjustScroll() { public autoAdjustScroll() {
const focusedColumn = this.grid.getLastFocusedColumn(); const focusedColumn = this.grid.getLastFocusedColumn();
if (focusedColumn === null) { if (focusedColumn === null || focusedColumn.grid !== this.grid) {
this.removeOverscroll();
return;
}
if (focusedColumn.grid !== this.grid) {
return; return;
} }
this.scrollToColumn(focusedColumn); this.scrollToColumn(focusedColumn);
} }
private getScrollPos(scrollX: number) { public scrollToColumn(column: Column) {
return new Desktop.ScrollPos(scrollX, this.tilingArea.width); if (this.dirtyScroll || !column.isVisible(this.getCurrentVisibleRange(), true)) {
this.config.scroller.scrollToColumn(this, column);
}
} }
public getCurrentScrollPos() { private getVisibleRange(scrollX: number) {
return this.getScrollPos(this.scrollX); return new Desktop.RangeImpl(scrollX, this.tilingArea.width);
}
public getCurrentVisibleRange() {
return this.getVisibleRange(this.scrollX);
} }
private clampScrollX(x: number) { private clampScrollX(x: number) {
let minScroll = 0; return this.config.scroller.clampScrollX(this, x);
let maxScroll = this.grid.getWidth() - this.tilingArea.width;
if (maxScroll < 0) {
const centerScroll = Math.round(maxScroll / 2);
minScroll = centerScroll;
maxScroll = centerScroll;
}
return clamp(x, minScroll, maxScroll);
} }
private setScroll(x: number, force: boolean) { public setScroll(x: number, force: boolean) {
const oldScrollX = this.scrollX; const oldScrollX = this.scrollX;
this.scrollX = force ? x : this.clampScrollX(x); this.scrollX = force ? x : this.clampScrollX(x);
if (this.scrollX !== oldScrollX) { if (this.scrollX !== oldScrollX) {
this.onLayoutChanged(); this.onLayoutChanged();
} }
} this.dirtyScroll = false;
private applyScrollPos(scrollPos: Desktop.ScrollPos) {
this.setScroll(scrollPos.x, true);
} }
public adjustScroll(dx: number, force: boolean) { public adjustScroll(dx: number, force: boolean) {
this.setScroll(this.scrollX + dx, force); this.setScroll(this.scrollX + dx, force);
} }
private removeOverscroll() { public equalizeVisibleColumnsWidths() {
this.setScroll(this.scrollX, false); const visibleRange = this.getCurrentVisibleRange();
const visibleColumns = Array.from(this.grid.getVisibleColumns(visibleRange, true));
let remainingWidth = this.tilingArea.width - (visibleColumns.length-1) * this.grid.config.gapsInnerHorizontal;
let remainingColumns = visibleColumns.length;
const minWidths = visibleColumns.map(column => column.getMinWidth()).sort((a, b) => b - a);
for (const minWidth of minWidths) {
if (minWidth > remainingWidth / remainingColumns) {
remainingWidth -= minWidth;
remainingColumns--;
}
}
const avgWidth = remainingWidth / remainingColumns;
for (const column of visibleColumns) {
const minWidth = column.getMinWidth();
if (minWidth > avgWidth) {
column.setWidth(minWidth, true);
} else {
const columnWidth = Math.round(remainingWidth / remainingColumns);
column.setWidth(columnWidth, true);
remainingWidth -= column.getWidth();
remainingColumns--;
}
}
this.scrollCenterRange(Desktop.RangeImpl.fromRanges(
visibleColumns[0],
visibleColumns[visibleColumns.length - 1],
));
} }
public arrange() { public arrange() {
@@ -151,16 +168,18 @@ class Desktop {
if (!this.dirty) { if (!this.dirty) {
return; return;
} }
this.grid.arrange(this.tilingArea.x - this.scrollX); this.grid.arrange(this.tilingArea.x - this.scrollX, this.getCurrentVisibleRange());
this.dirty = false; this.dirty = false;
} }
public onLayoutChanged() { public onLayoutChanged() {
this.dirty = true; this.dirty = true;
this.dirtyScroll = true;
} }
public onPinsChanged() { public onPinsChanged() {
this.dirty = true; this.dirty = true;
this.dirtyScroll = true;
this.dirtyPins = true; this.dirtyPins = true;
} }
@@ -175,12 +194,18 @@ namespace Desktop {
marginBottom: number, marginBottom: number,
marginLeft: number, marginLeft: number,
marginRight: number, marginRight: number,
overscroll: number, scroller: Desktop.Scroller,
}; };
export class ScrollPos { export type Range = {
public readonly x: number; getLeft(): number;
public readonly width: number; getRight(): number;
getWidth(): number;
}
export class RangeImpl {
private readonly x: number;
private readonly width: number;
constructor(x: number, width: number) { constructor(x: number, width: number) {
this.x = x; this.x = x;
@@ -194,5 +219,93 @@ namespace Desktop {
public getRight() { public getRight() {
return this.x + this.width; 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;
private width: number;
constructor(initialColumn: Column) {
this.left = initialColumn;
this.right = initialColumn;
this.width = initialColumn.getWidth();
}
public addNeighbors(visibleRange: Desktop.Range, gap: number, condition: (column: Column) => boolean) {
const grid = this.left.grid;
const columnRange = this;
function canFit(column: Column) {
return columnRange.width + gap + column.getWidth() <= visibleRange.getWidth()
}
function isUsable(column: Column|null) {
return column !== null &&
canFit(column) &&
condition(column)
}
let leftColumn = grid.getPrevColumn(this.left);
let rightColumn = grid.getNextColumn(this.right);
function checkColumns() {
if (!isUsable(leftColumn)) {
leftColumn = null;
}
if (!isUsable(rightColumn)) {
rightColumn = null;
}
}
checkColumns();
while (leftColumn !== null || rightColumn !== null) {
const leftWidth = leftColumn === null ? 0 : leftColumn.getWidth();
const rightWidth = rightColumn === null ? 0 : rightColumn.getWidth();
if (leftWidth > rightWidth) {
this.addLeft(leftColumn!, gap);
leftColumn = grid.getPrevColumn(leftColumn!);
} else {
this.addRight(rightColumn!, gap);
rightColumn = grid.getNextColumn(rightColumn!);
}
checkColumns();
}
}
public addLeft(column: Column, gap: number) {
this.left = column;
this.width += column.getWidth() + gap;
}
public addRight(column: Column, gap: number) {
this.right = column;
this.width += column.getWidth() + gap;
}
public getLeft() {
return this.left.getLeft();
}
public getRight() {
return this.right.getRight();
}
public getWidth() {
return this.width;
}
}
export type Scroller = {
scrollToColumn(desktop: Desktop, column: Column): void;
clampScrollX(desktop: Desktop, x: number): number;
} }
} }

View File

@@ -1,3 +1,5 @@
import Range = Desktop.Range;
class Grid { class Grid {
public readonly desktop: Desktop; public readonly desktop: Desktop;
public readonly config: LayoutConfig; public readonly config: LayoutConfig;
@@ -88,39 +90,41 @@ class Grid {
this.width = x - this.config.gapsInnerHorizontal; this.width = x - this.config.gapsInnerHorizontal;
} }
public getLeftmostVisibleColumn(scrollPos: Desktop.ScrollPos, fullyVisible: boolean) { public getLeftmostVisibleColumn(visibleRange: Desktop.Range, fullyVisible: boolean) {
const scrollX = scrollPos.getLeft();
for (const column of this.columns.iterator()) { for (const column of this.columns.iterator()) {
const x = fullyVisible ? column.getLeft() : column.getRight() + (this.config.gapsInnerHorizontal - 1); if (column.isVisible(visibleRange, fullyVisible)) {
if (x >= scrollX) {
return column; return column;
} }
} }
return null; return null;
} }
public getRightmostVisibleColumn(scrollPos: Desktop.ScrollPos, fullyVisible: boolean) { public getRightmostVisibleColumn(visibleRange: Desktop.Range, fullyVisible: boolean) {
const scrollX = scrollPos.getRight();
let last = null; let last = null;
for (const column of this.columns.iterator()) { for (const column of this.columns.iterator()) {
const x = fullyVisible ? column.getRight() : column.getLeft() - (this.config.gapsInnerHorizontal - 1); if (column.isVisible(visibleRange, fullyVisible)) {
if (x <= scrollX) {
last = column; last = column;
} else { } else if (last !== null) {
break; break;
} }
} }
return last; return last;
} }
public getVisibleColumnsWidth(scrollPos: Desktop.ScrollPos, fullyVisible: boolean) { public *getVisibleColumns(visibleRange: Desktop.Range, fullyVisible: boolean) {
for (const column of this.columns.iterator()) {
if (column.isVisible(visibleRange, fullyVisible)) {
yield column;
}
}
}
public getVisibleColumnsWidth(visibleRange: Desktop.Range, fullyVisible: boolean) {
let width = 0; let width = 0;
let nVisible = 0; let nVisible = 0;
for (const column of this.columns.iterator()) { for (const column of this.getVisibleColumns(visibleRange, fullyVisible)) {
if (column.isVisible(scrollPos, fullyVisible)) { width += column.getWidth();
width += column.getWidth(); nVisible++;
nVisible++;
}
} }
if (nVisible > 0) { if (nVisible > 0) {
@@ -130,84 +134,9 @@ class Grid {
return width; return width;
} }
private getLeftOffScreenColumn(scrollPos: Desktop.ScrollPos) { public arrange(x: number, visibleRange: Range) {
const leftVisible = this.getLeftmostVisibleColumn(scrollPos, true);
if (leftVisible === null) {
return null;
}
return this.getPrevColumn(leftVisible);
}
private getRightOffScreenColumn(scrollPos: Desktop.ScrollPos) {
const rightVisible = this.getRightmostVisibleColumn(scrollPos, true);
if (rightVisible === null) {
return null;
}
return this.getNextColumn(rightVisible);
}
public increaseColumnWidth(column: Column) {
const scrollPos = this.desktop.getScrollPosForColumn(column);
if (this.width < scrollPos.width) {
column.adjustWidth(scrollPos.width - this.width, true);
return;
}
let leftColumn = this.getLeftmostVisibleColumn(scrollPos, false);
if (leftColumn === column) {
leftColumn = null;
}
let rightColumn = this.getRightmostVisibleColumn(scrollPos, false);
if (rightColumn === column) {
rightColumn = null;
}
if (leftColumn === null && rightColumn === null) {
return;
}
const leftVisibleWidth = leftColumn === null ? Infinity : leftColumn.getRight() - scrollPos.getLeft();
const rightVisibleWidth = rightColumn === null ? Infinity : scrollPos.getRight() - rightColumn.getLeft();
const expandLeft = leftVisibleWidth < rightVisibleWidth;
const widthDelta = (expandLeft ? leftVisibleWidth : rightVisibleWidth) + this.config.gapsInnerHorizontal;
if (expandLeft) {
this.desktop.adjustScroll(widthDelta, false);
}
column.adjustWidth(widthDelta, true);
}
public decreaseColumnWidth(column: Column) {
const scrollPos = this.desktop.getScrollPosForColumn(column);
if (this.width <= scrollPos.width) {
column.setWidth(Math.round(column.getWidth() / 2), true);
return;
}
let leftColumn = this.getLeftOffScreenColumn(scrollPos);
if (leftColumn === column) {
leftColumn = null;
}
let rightColumn = this.getRightOffScreenColumn(scrollPos);
if (rightColumn === column) {
rightColumn = null;
}
if (leftColumn === null && rightColumn === null) {
return;
}
const leftInvisibleWidth = leftColumn === null ? Infinity : scrollPos.getLeft() - leftColumn.getLeft();
const rightInvisibleWidth = rightColumn === null ? Infinity : rightColumn.getRight() - scrollPos.getRight();
const shrinkLeft = leftInvisibleWidth < rightInvisibleWidth;
const widthDelta = (shrinkLeft ? leftInvisibleWidth : rightInvisibleWidth);
if (shrinkLeft) {
const maxDelta = column.getWidth() - column.getMinWidth();
this.desktop.adjustScroll(-Math.min(widthDelta, maxDelta), false);
}
column.adjustWidth(-widthDelta, true);
}
public arrange(x: number) {
for (const column of this.columns.iterator()) { for (const column of this.columns.iterator()) {
column.arrange(x); column.arrange(x, visibleRange, this.userResize);
x += column.getWidth() + this.config.gapsInnerHorizontal; x += column.getWidth() + this.config.gapsInnerHorizontal;
} }
@@ -239,12 +168,12 @@ class Grid {
this.columns.remove(column); this.columns.remove(column);
this.columnsSetX(nextColumn); this.columnsSetX(nextColumn);
this.desktop.onLayoutChanged();
if (passFocus && columnToFocus !== null) { if (passFocus && columnToFocus !== null) {
columnToFocus.focus(); columnToFocus.focus();
} else { } else {
this.desktop.autoAdjustScroll(); this.desktop.autoAdjustScroll();
} }
this.desktop.onLayoutChanged();
} }
public onColumnMoved(column: Column, prevColumn: Column|null) { public onColumnMoved(column: Column, prevColumn: Column|null) {
@@ -259,10 +188,10 @@ class Grid {
public onColumnWidthChanged(column: Column, oldWidth: number, width: number) { public onColumnWidthChanged(column: Column, oldWidth: number, width: number) {
const nextColumn = this.columns.getNext(column); const nextColumn = this.columns.getNext(column);
this.columnsSetX(nextColumn); this.columnsSetX(nextColumn);
this.desktop.onLayoutChanged();
if (!this.userResize) { if (!this.userResize) {
this.desktop.autoAdjustScroll(); this.desktop.autoAdjustScroll();
} }
this.desktop.onLayoutChanged();
} }
public onColumnFocused(column: Column) { public onColumnFocused(column: Column) {

View File

@@ -1,9 +1,11 @@
type LayoutConfig = { type LayoutConfig = {
gapsInnerHorizontal: number, gapsInnerHorizontal: number,
gapsInnerVertical: number, gapsInnerVertical: number,
offScreenOpacity: number,
stackColumnsByDefault: boolean, stackColumnsByDefault: boolean,
resizeNeighborColumn: boolean, resizeNeighborColumn: boolean,
reMaximize: boolean, reMaximize: boolean,
skipSwitcher: boolean,
tiledKeepBelow: boolean, tiledKeepBelow: boolean,
maximizedKeepAbove: boolean, maximizedKeepAbove: boolean,
}; };

View File

@@ -0,0 +1,21 @@
class ScrollerCentered {
public scrollToColumn(desktop: Desktop, column: Column) {
desktop.scrollCenterRange(column);
}
public clampScrollX(desktop: Desktop, x: number) {
return ScrollerCentered.clampScrollX(desktop, x);
}
public static clampScrollX(desktop: Desktop, x: number) {
const firstColumn = desktop.grid.getFirstColumn();
if (firstColumn === null) {
return 0;
}
const lastColumn = desktop.grid.getLastColumn()!;
let minScroll = Math.round((firstColumn.getWidth() - desktop.tilingArea.width) / 2);
let maxScroll = Math.round(desktop.grid.getWidth() - (desktop.tilingArea.width + lastColumn.getWidth()) / 2);
return clamp(x, minScroll, maxScroll);
}
}

View File

@@ -0,0 +1,9 @@
class ScrollerGrouped {
public scrollToColumn(desktop: Desktop, column: Column) {
desktop.scrollCenterVisible(column, true);
}
public clampScrollX(desktop: Desktop, x: number) {
return ScrollerCentered.clampScrollX(desktop, x);
}
}

View File

@@ -0,0 +1,14 @@
class ScrollerLazy {
public scrollToColumn(desktop: Desktop, column: Column) {
desktop.scrollIntoView(column);
}
public clampScrollX(desktop: Desktop, x: number) {
let minScroll = 0;
let maxScroll = desktop.grid.getWidth() - desktop.tilingArea.width;
if (maxScroll < 0) {
return Math.round(maxScroll / 2);
}
return clamp(x, minScroll, maxScroll);
}
}

View File

@@ -2,7 +2,7 @@ class Window {
public column: Column; public column: Column;
public readonly client: ClientWrapper; public readonly client: ClientWrapper;
public height: number; public height: number;
public readonly focusedState: WindowState; public readonly focusedState: Window.State;
private skipArrange: boolean; private skipArrange: boolean;
constructor(client: ClientWrapper, column: Column) { constructor(client: ClientWrapper, column: Column) {
@@ -106,7 +106,7 @@ class Window {
this.column.grid.desktop.onLayoutChanged(); this.column.grid.desktop.onLayoutChanged();
} }
public onUserResize(oldGeometry: QRect, resizeNeighborColumn: boolean) { public onUserResize(oldGeometry: QmlRect, resizeNeighborColumn: boolean) {
const newGeometry = this.client.kwinClient.frameGeometry; const newGeometry = this.client.kwinClient.frameGeometry;
const widthDelta = newGeometry.width - oldGeometry.width; const widthDelta = newGeometry.width - oldGeometry.width;
const heightDelta = newGeometry.height - oldGeometry.height; const heightDelta = newGeometry.height - oldGeometry.height;
@@ -142,8 +142,10 @@ class Window {
} }
} }
type WindowState = { namespace Window {
fullScreen: boolean, export type State = {
maximizedHorizontally: boolean, fullScreen: boolean,
maximizedVertically: boolean, maximizedHorizontally: boolean,
maximizedVertically: boolean,
}
} }

View File

@@ -1,5 +1,5 @@
class Delayer { class Delayer {
private readonly timer: QQmlTimer; private readonly timer: QmlTimer;
constructor(delay: number, f: () => void) { constructor(delay: number, f: () => void) {
this.timer = initQmlTimer(); this.timer = initQmlTimer();

View File

@@ -38,7 +38,7 @@ function initWorkspaceSignalHandlers(world: World) {
if ((horizontally || vertically) && kwinClient.tile !== null) { if ((horizontally || vertically) && kwinClient.tile !== null) {
kwinClient.tile = null; kwinClient.tile = null;
} }
world.doIfTiled(kwinClient, false, (world, desktopManager, window, column, grid) => { world.doIfTiled(kwinClient, false, (clientManager, desktopManager, window, column, grid) => {
window.onMaximizedChanged(horizontally, vertically); window.onMaximizedChanged(horizontally, vertically);
}); });
}); });

View File

@@ -6,7 +6,7 @@ class ClientWrapper {
private readonly rulesSignalManager: SignalManager | null; private readonly rulesSignalManager: SignalManager | null;
public preferredWidth: number; public preferredWidth: number;
private readonly manipulatingGeometry: Doer; private readonly manipulatingGeometry: Doer;
private lastPlacement: QRect | null; // workaround for issue #19 private lastPlacement: QmlRect | null; // workaround for issue #19
constructor( constructor(
kwinClient: KwinClient, kwinClient: KwinClient,
@@ -92,7 +92,7 @@ class ClientWrapper {
return this.kwinClient.shade; return this.kwinClient.shade;
} }
public isManipulatingGeometry(newGeometry: QRect | null) { public isManipulatingGeometry(newGeometry: QmlRect | null) {
if (newGeometry !== null && newGeometry === this.lastPlacement) { if (newGeometry !== null && newGeometry === this.lastPlacement) {
return true; return true;
} }
@@ -108,7 +108,7 @@ class ClientWrapper {
this.transients.splice(i, 1); this.transients.splice(i, 1);
} }
public ensureTransientsVisible(screenSize: QRect) { public ensureTransientsVisible(screenSize: QmlRect) {
for (const transient of this.transients) { for (const transient of this.transients) {
if (transient.stateManager.getState() instanceof ClientState.Floating) { if (transient.stateManager.getState() instanceof ClientState.Floating) {
transient.ensureVisible(screenSize); transient.ensureVisible(screenSize);
@@ -117,15 +117,15 @@ class ClientWrapper {
} }
} }
public ensureVisible(screenSize: QRect) { public ensureVisible(screenSize: QmlRect) {
if (this.kwinClient.desktop !== workspace.currentDesktop) { if (this.kwinClient.desktop !== workspace.currentDesktop) {
return; return;
} }
const frame = this.kwinClient.frameGeometry; const frame = this.kwinClient.frameGeometry;
if (frame.left < 0) { if (frame.left < screenSize.left) {
frame.x = 0; frame.x = screenSize.left;
} else if (frame.right > screenSize.width) { } else if (frame.right > screenSize.right) {
frame.x = screenSize.width - frame.width; frame.x = screenSize.right - frame.width;
} }
} }

View File

@@ -13,7 +13,7 @@ class PinManager {
this.pinnedClients.delete(kwinClient); this.pinnedClients.delete(kwinClient);
} }
public getAvailableSpace(desktopNumber: number, screen: QRect) { public getAvailableSpace(desktopNumber: number, screen: QmlRect) {
const baseLot = new PinManager.Lot(screen.top, screen.bottom, screen.left, screen.right); const baseLot = new PinManager.Lot(screen.top, screen.bottom, screen.left, screen.right);
let lots = [baseLot]; let lots = [baseLot];
for (const client of this.pinnedClients) { for (const client of this.pinnedClients) {
@@ -53,7 +53,7 @@ namespace PinManager {
public readonly right: number, public readonly right: number,
) {} ) {}
public split(destLots: Lot[], obstacle: QRect) { public split(destLots: Lot[], obstacle: QmlRect) {
if (!this.contains(obstacle)) { if (!this.contains(obstacle)) {
// don't split // don't split
destLots.push(this); destLots.push(this);
@@ -74,9 +74,9 @@ namespace PinManager {
} }
} }
private contains(obstacle: QRect) { private contains(obstacle: QmlRect) {
return obstacle.right >= this.left && obstacle.left <= this.right && return obstacle.right > this.left && obstacle.left < this.right &&
obstacle.bottom >= this.top && obstacle.top <= this.bottom; obstacle.bottom > this.top && obstacle.top < this.bottom;
} }
public area() { public area() {

View File

@@ -21,6 +21,18 @@ class World {
this.pinManager = new PinManager(); this.pinManager = new PinManager();
const layoutConfig = {
gapsInnerHorizontal: config.gapsInnerHorizontal,
gapsInnerVertical: config.gapsInnerVertical,
offScreenOpacity: config.offScreenOpacity / 100.0,
stackColumnsByDefault: config.stackColumnsByDefault,
resizeNeighborColumn: config.resizeNeighborColumn,
reMaximize: config.reMaximize,
skipSwitcher: config.skipSwitcher,
tiledKeepBelow: config.tiledKeepBelow,
maximizedKeepAbove: config.floatingKeepAbove,
};
this.desktopManager = new DesktopManager( this.desktopManager = new DesktopManager(
this.pinManager, this.pinManager,
{ {
@@ -28,17 +40,12 @@ class World {
marginBottom: config.gapsOuterBottom, marginBottom: config.gapsOuterBottom,
marginLeft: config.gapsOuterLeft, marginLeft: config.gapsOuterLeft,
marginRight: config.gapsOuterRight, marginRight: config.gapsOuterRight,
overscroll: config.overscroll, scroller: config.scrollingLazy ? new ScrollerLazy() :
}, config.scrollingCentered ? new ScrollerCentered() :
{ config.scrollingGrouped ? new ScrollerGrouped() :
gapsInnerHorizontal: config.gapsInnerHorizontal, console.assert(false),
gapsInnerVertical: config.gapsInnerVertical,
stackColumnsByDefault: config.stackColumnsByDefault,
resizeNeighborColumn: config.resizeNeighborColumn,
reMaximize: config.reMaximize,
tiledKeepBelow: config.tiledKeepBelow,
maximizedKeepAbove: config.floatingKeepAbove,
}, },
layoutConfig,
workspace.currentActivity, workspace.currentActivity,
); );
this.clientManager = new ClientManager(config, this, this.desktopManager, this.pinManager); this.clientManager = new ClientManager(config, this, this.desktopManager, this.pinManager);

View File

@@ -16,7 +16,7 @@ namespace ClientState {
private static initSignalManager(world: World, kwinClient: KwinClient) { private static initSignalManager(world: World, kwinClient: KwinClient) {
const manager = new SignalManager(); const manager = new SignalManager();
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: KwinClient, oldGeometry: QRect) => { manager.connect(kwinClient.frameGeometryChanged, (kwinClient: KwinClient, oldGeometry: QmlRect) => {
world.onScreenResized(); world.onScreenResized();
}); });
return manager; return manager;

View File

@@ -21,6 +21,6 @@ namespace ClientState {
} }
export type State = { export type State = {
destroy(passFocus: boolean): void; destroy(passFocus: boolean): void,
}; };
} }

View File

@@ -41,7 +41,7 @@ namespace ClientState {
} }
}); });
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: KwinClient, oldGeometry: QRect) => { manager.connect(kwinClient.frameGeometryChanged, (kwinClient: KwinClient, oldGeometry: QmlRect) => {
if (kwinClient.tile === null) { if (kwinClient.tile === null) {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
clientManager.unpinClient(kwinClient); clientManager.unpinClient(kwinClient);

View File

@@ -1,9 +1,11 @@
namespace ClientState { namespace ClientState {
export class Tiled implements State { export class Tiled implements State {
public readonly window: Window; public readonly window: Window;
private readonly defaultState: Tiled.WindowState;
private readonly signalManager: SignalManager; private readonly signalManager: SignalManager;
constructor(world: World, client: ClientWrapper, grid: Grid) { constructor(world: World, client: ClientWrapper, grid: Grid) {
this.defaultState = { skipSwitcher: client.kwinClient.skipSwitcher };
Tiled.prepareClientForTiling(client, grid.config); Tiled.prepareClientForTiling(client, grid.config);
const column = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn()); const column = new Column(grid, grid.getLastFocusedColumn() ?? grid.getLastColumn());
@@ -21,7 +23,7 @@ namespace ClientState {
const client = window.client; const client = window.client;
window.destroy(passFocus); window.destroy(passFocus);
Tiled.restoreClientAfterTiling(client, grid.config, grid.desktop.clientArea); Tiled.restoreClientAfterTiling(client, grid.config, this.defaultState, grid.desktop.clientArea);
} }
private static initSignalManager(world: World, window: Window) { private static initSignalManager(world: World, window: Window) {
@@ -81,7 +83,7 @@ namespace ClientState {
cursorChangedAfterResizeStart = false; cursorChangedAfterResizeStart = false;
}); });
manager.connect(kwinClient.frameGeometryChanged, (kwinClient: KwinClient, oldGeometry: QRect) => { manager.connect(kwinClient.frameGeometryChanged, (kwinClient: KwinClient, oldGeometry: QmlRect) => {
// on Wayland, this fires after `tileChanged` // on Wayland, this fires after `tileChanged`
if (kwinClient.tile !== null) { if (kwinClient.tile !== null) {
world.do((clientManager, desktopManager) => { world.do((clientManager, desktopManager) => {
@@ -143,6 +145,9 @@ namespace ClientState {
} }
private static prepareClientForTiling(client: ClientWrapper, config: LayoutConfig) { private static prepareClientForTiling(client: ClientWrapper, config: LayoutConfig) {
if (config.skipSwitcher) {
client.kwinClient.skipSwitcher = true;
}
if (config.tiledKeepBelow) { if (config.tiledKeepBelow) {
client.kwinClient.keepBelow = true; client.kwinClient.keepBelow = true;
} }
@@ -153,7 +158,10 @@ namespace ClientState {
client.setMaximize(false, false); client.setMaximize(false, false);
} }
private static restoreClientAfterTiling(client: ClientWrapper, config: LayoutConfig, screenSize: QRect) { private static restoreClientAfterTiling(client: ClientWrapper, config: LayoutConfig, defaultState: Tiled.WindowState, screenSize: QmlRect) {
if (config.skipSwitcher) {
client.kwinClient.skipSwitcher = defaultState.skipSwitcher;
}
if (config.tiledKeepBelow) { if (config.tiledKeepBelow) {
client.kwinClient.keepBelow = false; client.kwinClient.keepBelow = false;
} }
@@ -165,4 +173,10 @@ namespace ClientState {
client.ensureVisible(screenSize); client.ensureVisible(screenSize);
} }
} }
namespace Tiled {
export type WindowState = {
skipSwitcher: boolean,
}
}
} }