shift-shell/components/mobileshell/qml/actiondrawer/ActionDrawerOpenSurface.qml
Marco Allegretti 1a70b24c06 Convergence: stabilize workspace scroll gestures
Improve touchpad scroll handling for convergence workflows while avoiding gesture conflicts.

- make SwipeArea handle angleDelta-based touchpad wheel streams and phase-less sequences safely
- wire topbar ActionDrawerOpenSurface to virtual desktop info and horizontal workspace switching logic
- add dock pager wheel switching in FavouritesBar for the vertical wheel axis environments deliver
- prevent wallpaper/home surface touchpad scroll from opening app drawer in convergence
- extend convergence invariant checks for the new gesture paths
2026-05-25 21:59:19 +02:00

179 lines
5.9 KiB
QML

/*
* SPDX-FileCopyrightText: 2021-2024 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
import org.kde.plasma.private.mobileshell as MobileShell
/**
* Component that triggers the opening and closing of an ActionDrawer when dragged on with touch or mouse.
*/
MobileShell.SwipeArea {
id: root
mode: MobileShell.SwipeArea.VerticalOnly
required property ActionDrawer actionDrawer
property var virtualDesktopInfo: null
readonly property real touchpadDirectionLockThreshold: 12
readonly property real touchpadWorkspaceSwitchThreshold: 80
readonly property real touchpadAxisDominance: 1.25
property point touchpadStartPoint: Qt.point(0, 0)
property string touchpadGestureMode: ""
property bool touchpadWorkspaceSwitched: false
function startSwipe() {
if (actionDrawer.intendedToBeVisible) {
// ensure the action drawer state is consistent
actionDrawer.closeImmediately();
}
actionDrawer.cancelAnimations();
actionDrawer.dragging = true;
actionDrawer.opened = false;
// must be after properties other are set, we cannot have actionDrawer.updateState() be called
actionDrawer.offset = 0;
actionDrawer.oldOffset = 0;
actionDrawer.intendedToBeVisible = true;
}
function startSwipeWithPoint(point) {
if (ShellSettings.Settings.convergenceModeEnabled) {
actionDrawer.openToPinnedMode = false;
} else if (point.x < root.width / 2) {
actionDrawer.openToPinnedMode = ShellSettings.Settings.actionDrawerTopLeftMode == ShellSettings.Settings.Pinned;
} else {
actionDrawer.openToPinnedMode = ShellSettings.Settings.actionDrawerTopRightMode == ShellSettings.Settings.Pinned;
}
startSwipe();
}
function endSwipe() {
actionDrawer.dragging = false;
actionDrawer.updateState();
}
function updateOffset(offsetY) {
actionDrawer.offset += offsetY;
}
function workspaceScrollAvailable() {
return ShellSettings.Settings.convergenceModeEnabled
&& virtualDesktopInfo !== null
&& virtualDesktopInfo.numberOfDesktops > 1;
}
function desktopIndexForId(desktopId) {
if (virtualDesktopInfo === null || !virtualDesktopInfo.desktopIds) {
return -1;
}
for (let i = 0; i < virtualDesktopInfo.desktopIds.length; ++i) {
if (String(virtualDesktopInfo.desktopIds[i]) === String(desktopId)) {
return i;
}
}
return -1;
}
function activateAdjacentWorkspace(direction) {
if (!workspaceScrollAvailable()) {
return;
}
const currentIndex = desktopIndexForId(virtualDesktopInfo.currentDesktop);
if (currentIndex < 0) {
return;
}
const targetIndex = Math.max(0, Math.min(virtualDesktopInfo.desktopIds.length - 1, currentIndex + direction));
if (targetIndex !== currentIndex) {
virtualDesktopInfo.requestActivate(virtualDesktopInfo.desktopIds[targetIndex]);
}
}
function startTouchpadScroll(point) {
touchpadStartPoint = point;
touchpadGestureMode = "";
touchpadWorkspaceSwitched = false;
if (!ShellSettings.Settings.convergenceModeEnabled) {
touchpadGestureMode = "drawer";
startSwipeWithPoint(point);
}
}
function moveTouchpadScroll(totalDeltaX, totalDeltaY, deltaX, deltaY) {
if (touchpadGestureMode === "") {
const absX = Math.abs(totalDeltaX);
const absY = Math.abs(totalDeltaY);
if (absY >= touchpadDirectionLockThreshold && absY > absX * touchpadAxisDominance) {
touchpadGestureMode = "drawer";
startSwipeWithPoint(touchpadStartPoint);
updateOffset(totalDeltaY);
return;
} else if (workspaceScrollAvailable() && absX >= touchpadDirectionLockThreshold && absX > absY * touchpadAxisDominance) {
touchpadGestureMode = "workspace";
} else {
return;
}
}
if (touchpadGestureMode === "drawer") {
updateOffset(deltaY);
return;
}
if (!touchpadWorkspaceSwitched && Math.abs(totalDeltaX) >= touchpadWorkspaceSwitchThreshold) {
touchpadWorkspaceSwitched = true;
activateAdjacentWorkspace(totalDeltaX < 0 ? 1 : -1);
}
}
function endTouchpadScroll() {
if (touchpadGestureMode === "drawer") {
endSwipe();
}
touchpadGestureMode = "";
touchpadWorkspaceSwitched = false;
}
anchors.fill: parent
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: true
scrollGestureEnabled: false
cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
}
onSwipeStarted: (point) => startSwipeWithPoint(point)
onSwipeEnded: endSwipe()
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
onTouchpadScrollStarted: (point) => startTouchpadScroll(point)
onTouchpadScrollEnded: endTouchpadScroll()
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveTouchpadScroll(totalDeltaX, totalDeltaY, deltaX, deltaY);
// In convergence mode, allow click to toggle the action drawer (mouse-friendly)
onClicked: {
if (ShellSettings.Settings.convergenceModeEnabled) {
if (actionDrawer.intendedToBeVisible) {
actionDrawer.close();
} else {
actionDrawer.openToPinnedMode = false;
actionDrawer.intendedToBeVisible = true;
actionDrawer.open();
}
}
}
}