Compare commits

..

5 commits

Author SHA1 Message Date
e5ec88a12b Improve auto-hide dock reveal
Expand the dock input region as soon as the reveal animation starts
rather than waiting for it to finish, so the cursor can reach dock
items while the panel is still sliding into view.

Introduce updateInputRegion() and call it from onDockOffsetChanged,
onShouldHideChanged, onActiveChanged, onWidthChanged, and onHeightChanged
so the input region is always consistent with the current state.

While hover-revealing with a maximised window present, switch the
LayerShell exclusion zone from -1 to dockHeight so the window tiles
away from the dock instead of being obscured underneath it.

Reduce the hover dwell timer from a hard-coded 300 ms to
Kirigami.Units.shortDuration so the dock is easier to summon.
2026-05-06 13:33:38 +02:00
daf6ec3fd6 Smooth convergence mode transitions
Animate dock item sizing and button opacity when toggling convergence
mode so the dock does not snap abruptly between states.

FavouritesBar: promote cell/icon/nav/pager/trash sizing properties to
writable and attach NumberAnimation Behaviors (InOutCubic, longDuration).
Home, Overview, and trash buttons fade in/out with an InOutQuad opacity
Behavior so they appear gradually rather than popping.

Dock overlay (main.qml): introduce an `active` guard property so the
window can be driven by opacity (InOutQuad, shortDuration) rather than
toggling visibility directly. Switch the dockOffset slide easing from
a directional InExpo/OutExpo pair to a single InOutCubic so the motion
feels symmetrical.

StatusBar: smooth the background colour and convergence affordance
transitions.
2026-05-06 13:33:25 +02:00
3fba9798e4 Add snap layouts shell setting
Expose snapLayoutsEnabled through MobileShellSettings and store it\nin plasmamobilerc.\n\nUse the setting in the Shift snap assist effect eligibility logic\nand add a Convergence KCM switch to control it.\n\nKeep the switch disabled when convergence is off, gaming mode is on,\nor dynamic tiling is enabled so window-placement ownership stays\nconsistent.
2026-05-06 11:44:54 +02:00
8d59ce6153 Add Convergence section to shell KCM
Group convergence controls in one place by adding Convergence Mode, Dynamic Tiling, and Auto Hide Panels to the Shell settings page. Move Auto Hide Panels out of General and keep Dynamic Tiling disabled when convergence is off or gaming mode is active.
2026-05-06 10:02:43 +02:00
4282b30a30 Refine convergence dock task menu
Show only task actions that make sense for the current window and shell mode. Hide free-geometry actions while dynamic tiling owns window placement, collapse hidden menu rows, and filter virtual desktop destinations.
2026-05-06 09:37:14 +02:00
7 changed files with 290 additions and 47 deletions

View file

@ -36,6 +36,10 @@ Item {
*/
property color backgroundColor: "transparent"
Behavior on backgroundColor {
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
}
/**
* Whether to show a second row of the status bar, with more information.
*/
@ -93,7 +97,12 @@ Item {
Rectangle {
anchors.fill: parent
color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1)
visible: ShellSettings.Settings.convergenceModeEnabled && statusBarHover.hovered
visible: opacity > 0
opacity: ShellSettings.Settings.convergenceModeEnabled && statusBarHover.hovered ? 1 : 0
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
}
}
HoverHandler {
@ -223,8 +232,8 @@ Item {
anchors.bottom: parent.bottom
anchors.bottomMargin: Kirigami.Units.smallSpacing
visible: ShellSettings.Settings.convergenceModeEnabled
opacity: statusBarHover.hovered ? 0.6 : 0.2
visible: ShellSettings.Settings.convergenceModeEnabled || opacity > 0
opacity: ShellSettings.Settings.convergenceModeEnabled ? (statusBarHover.hovered ? 0.6 : 0.2) : 0
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration }

View file

@ -47,6 +47,7 @@ MobileShellSettings::MobileShellSettings(QObject *parent)
Q_EMIT gamingModeEnabledChanged();
Q_EMIT gamingDismissHintEnabledChanged();
Q_EMIT dynamicTilingEnabledChanged();
Q_EMIT snapLayoutsEnabledChanged();
Q_EMIT allowLogoutChanged();
}
if (group.name() == LOCKSCREEN_CONFIG_GROUP) {
@ -290,6 +291,19 @@ void MobileShellSettings::setDynamicTilingEnabled(bool enabled)
m_config->sync();
}
bool MobileShellSettings::snapLayoutsEnabled() const
{
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
return group.readEntry("snapLayoutsEnabled", true);
}
void MobileShellSettings::setSnapLayoutsEnabled(bool enabled)
{
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
group.writeEntry("snapLayoutsEnabled", enabled, KConfigGroup::Notify);
m_config->sync();
}
void MobileShellSettings::updateNavigationBarsInPlasma()
{
// Do not update panels when not in Plasma Mobile

View file

@ -60,6 +60,9 @@ class MobileShellSettings : public QObject
// When false, KWin's native quick-tile behaviour is used unmodified.
Q_PROPERTY(bool dynamicTilingEnabled READ dynamicTilingEnabled WRITE setDynamicTilingEnabled NOTIFY dynamicTilingEnabledChanged)
// Snap layout picker — only meaningful in convergence mode when dynamic tiling is off.
Q_PROPERTY(bool snapLayoutsEnabled READ snapLayoutsEnabled WRITE setSnapLayoutsEnabled NOTIFY snapLayoutsEnabledChanged)
// logout dialog
Q_PROPERTY(bool allowLogout READ allowLogout READ allowLogout NOTIFY allowLogoutChanged)
@ -286,6 +289,14 @@ public:
bool dynamicTilingEnabled() const;
void setDynamicTilingEnabled(bool enabled);
/**
* Whether the SHIFT snap layout picker is enabled.
* Defaults to true; only takes effect in convergence mode when gaming mode
* and dynamic tiling are off.
*/
bool snapLayoutsEnabled() const;
void setSnapLayoutsEnabled(bool enabled);
/**
* Whether logout button is shown in the logout/shutdown dialog.
*/
@ -335,6 +346,7 @@ Q_SIGNALS:
void gamingModeEnabledChanged();
void gamingDismissHintEnabledChanged();
void dynamicTilingEnabledChanged();
void snapLayoutsEnabledChanged();
void allowLogoutChanged();
void lockscreenLeftButtonActionChanged();
void lockscreenRightButtonActionChanged();

View file

@ -36,13 +36,18 @@ MouseArea {
readonly property int totalItemCount: repeater.count + (showRunningTasks ? taskRepeater.count : 0)
// In convergence mode, size icons to fit the dock bar instead of using page grid cells
readonly property real dockCellWidth: convergenceMode ? root.height : folio.HomeScreenState.pageCellWidth
readonly property real dockCellHeight: convergenceMode ? root.height : folio.HomeScreenState.pageCellHeight
property real dockCellWidth: convergenceMode ? root.height : folio.HomeScreenState.pageCellWidth
property real dockCellHeight: convergenceMode ? root.height : folio.HomeScreenState.pageCellHeight
Behavior on dockCellWidth { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
Behavior on dockCellHeight { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
// Navigation buttons width (used to offset center positioning)
readonly property real navButtonWidth: convergenceMode ? root.height : 0
readonly property real dockItemInset: convergenceMode ? Math.max(2, Kirigami.Units.smallSpacing / 2) : 0
readonly property real dockIconSize: Math.min(root.height * 0.56, Kirigami.Units.iconSizes.large)
property real navButtonWidth: convergenceMode ? root.height : 0
property real dockItemInset: convergenceMode ? Math.max(2, Kirigami.Units.smallSpacing / 2) : 0
property real dockIconSize: Math.min(root.height * 0.56, Kirigami.Units.iconSizes.large)
Behavior on navButtonWidth { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
Behavior on dockItemInset { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
Behavior on dockIconSize { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
function dockItemColor(pressed, hovered, active) {
if (pressed) {
@ -90,10 +95,12 @@ MouseArea {
// Virtual desktop pager (convergence mode, 2+ desktops)
readonly property bool showPager: convergenceMode && virtualDesktopInfo.numberOfDesktops > 1
readonly property real pagerButtonWidth: showPager ? Math.min(root.height, Kirigami.Units.gridUnit * 2.5) : 0
property real pagerButtonWidth: showPager ? Math.min(root.height, Kirigami.Units.gridUnit * 2.5) : 0
readonly property int pagerLeftCount: showPager ? Math.ceil(virtualDesktopInfo.numberOfDesktops / 2) : 0
readonly property int pagerRightCount: showPager ? virtualDesktopInfo.numberOfDesktops - pagerLeftCount : 0
readonly property real trashButtonWidth: convergenceMode ? root.height : 0
property real trashButtonWidth: convergenceMode ? root.height : 0
Behavior on pagerButtonWidth { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
Behavior on trashButtonWidth { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
function pagerDesktopName(index) {
let names = virtualDesktopInfo.desktopNames
@ -102,6 +109,34 @@ MouseArea {
return i18n("Desktop %1", index + 1)
}
function pagerDesktopNameForId(desktopId) {
let ids = virtualDesktopInfo.desktopIds
if (!ids) {
return ""
}
for (let i = 0; i < ids.length; ++i) {
if (String(ids[i]) === String(desktopId)) {
return root.pagerDesktopName(i)
}
}
return ""
}
function menuDesktopIds(isOnAllDesktops) {
let ids = virtualDesktopInfo.desktopIds
if (!ids || ids.length <= 1) {
return []
}
let result = []
for (let i = 0; i < ids.length; ++i) {
if (isOnAllDesktops || String(ids[i]) !== String(virtualDesktopInfo.currentDesktop)) {
result.push(ids[i])
}
}
return result
}
// Returns the desktop ID of the pager button under screen-space x, or ""
function pagerButtonDesktopAt(x) {
if (!showPager) return ""
@ -176,7 +211,9 @@ MouseArea {
// Home button (convergence mode, left end)
Rectangle {
id: homeButton
visible: root.convergenceMode
visible: root.convergenceMode || opacity > 0
enabled: root.convergenceMode
opacity: root.convergenceMode ? 1 : 0
activeFocusOnTab: root.convergenceMode
anchors.left: parent.left
anchors.top: parent.top
@ -184,6 +221,10 @@ MouseArea {
width: root.navButtonWidth
color: "transparent"
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
}
Accessible.role: Accessible.Button
Accessible.name: i18n("Home")
Accessible.onPressAction: MobileShellState.ShellDBusClient.openHomeScreen()
@ -235,7 +276,9 @@ MouseArea {
// Overview button (convergence mode, right end)
Rectangle {
id: overviewButton
visible: root.convergenceMode
visible: root.convergenceMode || opacity > 0
enabled: root.convergenceMode
opacity: root.convergenceMode ? 1 : 0
activeFocusOnTab: root.convergenceMode
anchors.right: parent.right
anchors.top: parent.top
@ -243,6 +286,10 @@ MouseArea {
width: root.navButtonWidth
color: "transparent"
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
}
Accessible.role: Accessible.Button
Accessible.name: i18n("Overview")
Accessible.onPressAction: root.folio.triggerOverview()
@ -452,7 +499,9 @@ MouseArea {
Rectangle {
id: trashButton
visible: root.convergenceMode
visible: root.convergenceMode || opacity > 0
enabled: root.convergenceMode
opacity: root.convergenceMode ? 1 : 0
activeFocusOnTab: root.convergenceMode
x: root.width - root.navButtonWidth - root.trashButtonWidth
y: 0
@ -460,6 +509,10 @@ MouseArea {
height: root.height
color: "transparent"
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
}
Accessible.role: Accessible.Button
Accessible.name: i18n("Trash")
Accessible.onPressAction: Qt.openUrlExternally("trash:/")
@ -1228,6 +1281,10 @@ MouseArea {
readonly property bool isLocationBottom: folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom
readonly property string taskStorageId: root.runningTaskStorageId(taskDelegate.model)
readonly property bool isGroupParent: taskDelegate.model.IsGroupParent === true
readonly property bool dynamicTilingActive: root.convergenceMode && ShellSettings.Settings.dynamicTilingEnabled
readonly property bool showFreeGeometryActions: !taskDelegate.isGroupParent && !taskDelegate.dynamicTilingActive
readonly property bool canChangeVirtualDesktops: taskDelegate.model.IsVirtualDesktopsChangeable === true
Accessible.role: Accessible.Button
Accessible.name: taskDelegate.model.display || ""
@ -1430,52 +1487,123 @@ MouseArea {
id: taskContextMenu
popupType: T.Popup.Window
PC3.MenuItem {
icon.name: "window-new"
text: i18n("Open New Window")
visible: taskDelegate.model.CanLaunchNewInstance === true
height: visible ? implicitHeight : 0
onClicked: tasksModel.requestNewInstance(tasksModel.makeModelIndex(taskDelegate.index))
}
PC3.MenuItem {
icon.name: "window-pin"
text: i18n("Pin to Dock")
// repeater.count dependency forces re-evaluation when favourites change
visible: taskDelegate.taskStorageId !== "" && repeater.count >= 0 && !folio.FavouritesModel.containsApplication(taskDelegate.taskStorageId)
height: visible ? implicitHeight : 0
enabled: !folio.FolioSettings.lockLayout
onClicked: folio.FavouritesModel.addApplication(taskDelegate.taskStorageId)
}
Controls.MenuSeparator {
visible: taskDelegate.model.CanLaunchNewInstance === true
|| (taskDelegate.taskStorageId !== "" && repeater.count >= 0 && !folio.FavouritesModel.containsApplication(taskDelegate.taskStorageId))
height: visible ? implicitHeight : 0
}
PC3.MenuItem {
icon.name: "transform-move"
text: i18n("Move")
visible: taskDelegate.showFreeGeometryActions
height: visible ? implicitHeight : 0
enabled: taskDelegate.model.IsMovable === true
onClicked: tasksModel.requestMove(tasksModel.makeModelIndex(taskDelegate.index))
}
PC3.MenuItem {
icon.name: "transform-scale"
text: i18n("Resize")
visible: taskDelegate.showFreeGeometryActions
height: visible ? implicitHeight : 0
enabled: taskDelegate.model.IsResizable === true
onClicked: tasksModel.requestResize(tasksModel.makeModelIndex(taskDelegate.index))
}
PC3.MenuItem {
icon.name: taskDelegate.model.IsMinimized ? "window-restore" : "window-minimize"
text: taskDelegate.model.IsMinimized ? i18n("Restore") : i18n("Minimize")
enabled: taskDelegate.model.IsMinimizable === true
onClicked: tasksModel.requestToggleMinimized(tasksModel.makeModelIndex(taskDelegate.index))
}
PC3.MenuItem {
icon.name: taskDelegate.model.IsMaximized ? "window-restore" : "window-maximize"
text: taskDelegate.model.IsMaximized ? i18n("Restore") : i18n("Maximize")
visible: taskDelegate.model.IsGroupParent !== true
visible: taskDelegate.showFreeGeometryActions
height: visible ? implicitHeight : 0
enabled: taskDelegate.model.IsMaximizable === true
onClicked: tasksModel.requestToggleMaximized(tasksModel.makeModelIndex(taskDelegate.index))
}
PC3.MenuItem {
icon.name: "window-keep-above"
text: taskDelegate.model.IsKeepAbove ? i18n("Do Not Keep Above Others") : i18n("Keep Above Others")
visible: taskDelegate.showFreeGeometryActions
height: visible ? implicitHeight : 0
onClicked: tasksModel.requestToggleKeepAbove(tasksModel.makeModelIndex(taskDelegate.index))
}
PC3.MenuItem {
icon.name: "window-keep-below"
text: taskDelegate.model.IsKeepBelow ? i18n("Do Not Keep Below Others") : i18n("Keep Below Others")
visible: taskDelegate.showFreeGeometryActions
height: visible ? implicitHeight : 0
onClicked: tasksModel.requestToggleKeepBelow(tasksModel.makeModelIndex(taskDelegate.index))
}
PC3.MenuItem {
icon.name: "view-fullscreen"
text: taskDelegate.model.IsFullScreen ? i18n("Leave Fullscreen") : i18n("Fullscreen")
visible: taskDelegate.showFreeGeometryActions
height: visible ? implicitHeight : 0
enabled: taskDelegate.model.IsFullScreenable === true
onClicked: tasksModel.requestToggleFullScreen(tasksModel.makeModelIndex(taskDelegate.index))
}
PC3.MenuItem {
icon.name: "window-close"
text: {
var ids = taskDelegate.model.WinIdList
return (ids && ids.length > 1) ? i18n("Close All") : i18n("Close")
}
enabled: taskDelegate.model.IsClosable === true
onClicked: tasksModel.requestClose(tasksModel.makeModelIndex(taskDelegate.index))
}
Controls.MenuSeparator {
visible: root.showPager && taskDelegate.model.IsVirtualDesktopsChangeable === true
visible: taskDelegate.canChangeVirtualDesktops
height: visible ? implicitHeight : 0
}
PC3.MenuItem {
icon.name: "virtual-desktops"
text: taskDelegate.model.IsOnAllVirtualDesktops ? i18n("Show Only on Current Desktop") : i18n("Show on All Desktops")
visible: taskDelegate.canChangeVirtualDesktops && virtualDesktopInfo.numberOfDesktops > 1
height: visible ? implicitHeight : 0
onClicked: tasksModel.requestVirtualDesktops(tasksModel.makeModelIndex(taskDelegate.index),
taskDelegate.model.IsOnAllVirtualDesktops ? [virtualDesktopInfo.currentDesktop] : [])
}
Instantiator {
model: root.showPager && taskDelegate.model.IsVirtualDesktopsChangeable === true
? virtualDesktopInfo.desktopIds : []
model: root.showPager && taskDelegate.canChangeVirtualDesktops ? root.menuDesktopIds(taskDelegate.model.IsOnAllVirtualDesktops === true) : []
delegate: PC3.MenuItem {
required property int index
required property var modelData
text: i18n("Move to %1", root.pagerDesktopName(index))
enabled: String(modelData) !== String(virtualDesktopInfo.currentDesktop)
text: i18n("Move to %1", root.pagerDesktopNameForId(modelData))
onTriggered: tasksModel.requestVirtualDesktops(
tasksModel.makeModelIndex(taskDelegate.index), [modelData])
}
onObjectAdded: (idx, obj) => taskContextMenu.insertItem(taskContextMenu.count, obj)
onObjectRemoved: (idx, obj) => taskContextMenu.removeItem(obj)
}
PC3.MenuItem {
icon.name: "list-add"
text: i18n("Move to New Desktop")
visible: taskDelegate.canChangeVirtualDesktops
height: visible ? implicitHeight : 0
onClicked: tasksModel.requestNewVirtualDesktop(tasksModel.makeModelIndex(taskDelegate.index))
}
}
}
}

View file

@ -276,7 +276,10 @@ ContainmentItem {
// task panel containment; this window only provides the visible dock.
Window {
id: dockOverlay
visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
readonly property bool active: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
visible: active
opacity: active ? 1 : 0
color: "transparent"
width: Screen.width
height: MobileShell.Constants.convergenceDockHeight
@ -284,7 +287,7 @@ ContainmentItem {
LayerShell.Window.scope: "dock-overlay"
LayerShell.Window.layer: LayerShell.Window.LayerTop
LayerShell.Window.anchors: LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
LayerShell.Window.exclusionZone: -1
LayerShell.Window.exclusionZone: shouldReserveSpace ? dockHeight : -1
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityOnDemand
// Auto-hide: slide dock content off-screen when a window is
@ -301,6 +304,25 @@ ContainmentItem {
readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled
&& windowMaximizedTracker.showingWindow && !hoverRevealing
readonly property bool shouldReserveSpace: ShellSettings.Settings.autoHidePanelsEnabled
&& windowMaximizedTracker.showingWindow && hoverRevealing
function updateInputRegion() {
if (shouldHide && dockOffset >= dockHeight) {
MobileShell.ShellUtil.setInputRegion(dockOverlay,
Qt.rect(0, dockOverlay.height - revealStripHeight,
dockOverlay.width, revealStripHeight))
} else {
MobileShell.ShellUtil.setInputRegion(dockOverlay, Qt.rect(0, 0, 0, 0))
}
}
onActiveChanged: {
hoverRevealTimer.stop()
hoverRevealing = false
dockOffset = shouldHide ? dockHeight : 0
updateInputRegion()
}
onShouldHideChanged: {
if (shouldHide) {
@ -308,26 +330,23 @@ ContainmentItem {
} else {
dockOffset = 0
}
updateInputRegion()
}
// Narrow the input region to a strip at the screen edge when hidden
// so that app controls near the bottom edge are not accidentally
// intercepted. Mirrors the same pattern used by NavigationPanel.
onDockOffsetChanged: {
if (dockOffset >= dockHeight) {
MobileShell.ShellUtil.setInputRegion(dockOverlay,
Qt.rect(0, dockOverlay.height - revealStripHeight,
dockOverlay.width, revealStripHeight))
} else if (dockOffset === 0) {
MobileShell.ShellUtil.setInputRegion(dockOverlay, Qt.rect(0, 0, 0, 0))
}
updateInputRegion()
}
onWidthChanged: updateInputRegion()
onHeightChanged: updateInputRegion()
// Delay reveal by 300 ms so a quick edge graze does not pop the
// Delay reveal briefly so a quick edge graze does not pop the
// dock up mid-interaction with the underlying application.
Timer {
id: hoverRevealTimer
interval: 300
interval: Kirigami.Units.shortDuration
repeat: false
onTriggered: dockOverlay.hoverRevealing = true
}
@ -346,11 +365,15 @@ ContainmentItem {
Behavior on dockOffset {
NumberAnimation {
easing.type: dockOverlay.shouldHide ? Easing.InExpo : Easing.OutExpo
easing.type: Easing.InOutCubic
duration: Kirigami.Units.longDuration
}
}
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
}
Rectangle {
anchors.fill: parent
visible: !dockOverlay.shouldHide || dockOverlay.dockOffset < dockOverlay.dockHeight

View file

@ -48,21 +48,7 @@ KCM.SimpleKCM {
}
}
FormCard.FormDelegateSeparator { above: shellVibrationsButton; below: animationsSwitch }
FormCard.FormSwitchDelegate {
id: autoHidePanels
text: i18n("Auto Hide Panels")
description: i18n("Auto-hide the status and navigation panels to allow applications to always be in fullscreen.")
checked: ShellSettings.Settings.autoHidePanelsEnabled
onCheckedChanged: {
if (checked != ShellSettings.Settings.autoHidePanelsEnabled) {
ShellSettings.Settings.autoHidePanelsEnabled = checked;
}
}
}
FormCard.FormDelegateSeparator { above: autoHidePanels; below: doubleTapWakeup }
FormCard.FormDelegateSeparator { above: animationsSwitch; below: doubleTapWakeup }
FormCard.FormSwitchDelegate {
id: doubleTapWakeup
@ -77,6 +63,70 @@ KCM.SimpleKCM {
}
}
FormCard.FormHeader {
title: i18n("Convergence")
}
FormCard.FormCard {
FormCard.FormSwitchDelegate {
id: convergenceModeSwitch
text: i18n("Convergence Mode")
description: i18n("Use desktop-style window placement, titlebar controls, Overview, and the dock.")
checked: ShellSettings.Settings.convergenceModeEnabled
onCheckedChanged: {
if (checked != ShellSettings.Settings.convergenceModeEnabled) {
ShellSettings.Settings.convergenceModeEnabled = checked;
}
}
}
FormCard.FormDelegateSeparator { above: convergenceModeSwitch; below: dynamicTilingSwitch }
FormCard.FormSwitchDelegate {
id: dynamicTilingSwitch
text: i18n("Dynamic Tiling")
description: i18n("Automatically arrange windows in convergence mode. Disabled while convergence mode is off or gaming mode is active.")
enabled: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
checked: ShellSettings.Settings.dynamicTilingEnabled
onCheckedChanged: {
if (checked != ShellSettings.Settings.dynamicTilingEnabled) {
ShellSettings.Settings.dynamicTilingEnabled = checked;
}
}
}
FormCard.FormDelegateSeparator { above: dynamicTilingSwitch; below: snapLayoutsSwitch }
FormCard.FormSwitchDelegate {
id: snapLayoutsSwitch
text: i18n("Snap Layouts")
description: i18n("Show the snap layout picker from the maximize button. Disabled while convergence mode is off, gaming mode is active, or dynamic tiling is enabled.")
enabled: ShellSettings.Settings.convergenceModeEnabled
&& !ShellSettings.Settings.gamingModeEnabled
&& !ShellSettings.Settings.dynamicTilingEnabled
checked: ShellSettings.Settings.snapLayoutsEnabled
onCheckedChanged: {
if (checked != ShellSettings.Settings.snapLayoutsEnabled) {
ShellSettings.Settings.snapLayoutsEnabled = checked;
}
}
}
FormCard.FormDelegateSeparator { above: snapLayoutsSwitch; below: autoHidePanels }
FormCard.FormSwitchDelegate {
id: autoHidePanels
text: i18n("Auto Hide Panels")
description: i18n("Allow maximized or fullscreen applications to reclaim panel and dock space.")
checked: ShellSettings.Settings.autoHidePanelsEnabled
onCheckedChanged: {
if (checked != ShellSettings.Settings.autoHidePanelsEnabled) {
ShellSettings.Settings.autoHidePanelsEnabled = checked;
}
}
}
}
FormCard.FormHeader {
title: i18n("Status Bar")
}

View file

@ -24,6 +24,7 @@ KWinComponents.SceneEffect {
readonly property bool snapLayoutsEligible: ShellSettings.Settings.convergenceModeEnabled
&& !ShellSettings.Settings.gamingModeEnabled
&& !ShellSettings.Settings.dynamicTilingEnabled
&& ShellSettings.Settings.snapLayoutsEnabled
readonly property int hoverBarHeight: 30
readonly property int decorationButtonSize: 16
readonly property int decorationButtonSpacing: 8
@ -372,6 +373,12 @@ KWinComponents.SceneEffect {
effect.hideSnapLayouts();
}
}
function onSnapLayoutsEnabledChanged() {
if (!effect.snapLayoutsEligible) {
effect.hideSnapLayouts();
}
}
}
// Gap constant (must match shift-tiling)