mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 00:47:22 +00:00
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
This commit is contained in:
parent
30e3006e3f
commit
1a70b24c06
7 changed files with 253 additions and 20 deletions
|
|
@ -10,9 +10,12 @@
|
|||
#include <QObject>
|
||||
#include <QTabletEvent>
|
||||
#include <QTouchEvent>
|
||||
#include <QWheelEvent>
|
||||
|
||||
// how many pixels to move before it starts being registered as a swipe
|
||||
const int SWIPE_REGISTER_THRESHOLD = 10;
|
||||
const qreal WHEEL_STEP_PIXEL_DELTA = 48.0;
|
||||
const int TOUCHPAD_SCROLL_END_TIMEOUT = 160;
|
||||
|
||||
SwipeArea::SwipeArea(QQuickItem *parent)
|
||||
: QQuickItem{parent}
|
||||
|
|
@ -20,6 +23,10 @@ SwipeArea::SwipeArea(QQuickItem *parent)
|
|||
setAcceptTouchEvents(true);
|
||||
setAcceptedMouseButtons(Qt::LeftButton);
|
||||
setFiltersChildMouseEvents(true);
|
||||
|
||||
m_touchpadScrollEndTimer.setSingleShot(true);
|
||||
m_touchpadScrollEndTimer.setInterval(TOUCHPAD_SCROLL_END_TIMEOUT);
|
||||
connect(&m_touchpadScrollEndTimer, &QTimer::timeout, this, &SwipeArea::endTouchpadScroll);
|
||||
}
|
||||
|
||||
SwipeArea::Mode SwipeArea::mode() const
|
||||
|
|
@ -225,43 +232,71 @@ void SwipeArea::wheelEvent(QWheelEvent *event)
|
|||
|
||||
event->setAccepted(false);
|
||||
|
||||
const bool isTouchpad = event->deviceType() == QInputDevice::DeviceType::TouchPad;
|
||||
QPointF scrollDelta = event->pixelDelta();
|
||||
if (scrollDelta.isNull() && !event->angleDelta().isNull()) {
|
||||
scrollDelta = QPointF(event->angleDelta()) / QWheelEvent::DefaultDeltasPerStep * WHEEL_STEP_PIXEL_DELTA;
|
||||
}
|
||||
|
||||
switch (event->phase()) {
|
||||
case Qt::ScrollBegin:
|
||||
if (!m_touchpadScrolling) {
|
||||
if (isTouchpad && !m_touchpadScrolling) {
|
||||
event->accept();
|
||||
|
||||
m_touchpadScrolling = true;
|
||||
m_totalScrollDelta = QPointF{0, 0};
|
||||
Q_EMIT touchpadScrollStarted(event->points().first().position());
|
||||
startTouchpadScroll(event->points().first().position());
|
||||
}
|
||||
break;
|
||||
case Qt::ScrollEnd:
|
||||
if (m_touchpadScrolling) {
|
||||
m_touchpadScrolling = false;
|
||||
m_totalScrollDelta = QPointF{0, 0};
|
||||
Q_EMIT touchpadScrollEnded();
|
||||
endTouchpadScroll();
|
||||
event->accept();
|
||||
}
|
||||
break;
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// HACK: if it isn't the touchpad, we never get the isBeginEvent() and isEndEvent() events
|
||||
if (!m_touchpadScrolling) {
|
||||
if (!isTouchpad || scrollDelta.isNull()) {
|
||||
return;
|
||||
}
|
||||
startTouchpadScroll(event->points().first().position());
|
||||
}
|
||||
|
||||
for (auto &point : event->points()) {
|
||||
event->addPassiveGrabber(point, this);
|
||||
}
|
||||
|
||||
auto pixelDelta = event->pixelDelta();
|
||||
m_totalScrollDelta = QPointF{m_totalScrollDelta + pixelDelta};
|
||||
Q_EMIT touchpadScrollMove(m_totalScrollDelta.x(), m_totalScrollDelta.y(), pixelDelta.x(), pixelDelta.y());
|
||||
m_totalScrollDelta = QPointF{m_totalScrollDelta + scrollDelta};
|
||||
Q_EMIT touchpadScrollMove(m_totalScrollDelta.x(), m_totalScrollDelta.y(), scrollDelta.x(), scrollDelta.y());
|
||||
|
||||
m_touchpadScrollEndTimer.start();
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void SwipeArea::startTouchpadScroll(QPointF point)
|
||||
{
|
||||
if (m_touchpadScrolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_touchpadScrolling = true;
|
||||
m_totalScrollDelta = QPointF{0, 0};
|
||||
Q_EMIT touchpadScrollStarted(point);
|
||||
}
|
||||
|
||||
void SwipeArea::endTouchpadScroll()
|
||||
{
|
||||
if (!m_touchpadScrolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_touchpadScrollEndTimer.stop();
|
||||
m_touchpadScrolling = false;
|
||||
m_totalScrollDelta = QPointF{0, 0};
|
||||
Q_EMIT touchpadScrollEnded();
|
||||
}
|
||||
|
||||
void SwipeArea::setMoving(bool moving)
|
||||
{
|
||||
m_moving = moving;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <QPointerEvent>
|
||||
#include <QQmlListProperty>
|
||||
#include <QQuickItem>
|
||||
#include <QTimer>
|
||||
#include <QTouchEvent>
|
||||
|
||||
/**
|
||||
|
|
@ -84,6 +85,8 @@ private:
|
|||
void handlePressEvent(QPointerEvent *event, QPointF point);
|
||||
void handleReleaseEvent(QPointerEvent *event, QPointF point);
|
||||
void handleMoveEvent(QPointerEvent *event, QPointF point);
|
||||
void startTouchpadScroll(QPointF point);
|
||||
void endTouchpadScroll();
|
||||
|
||||
Mode m_mode = Mode::BothAxis;
|
||||
bool m_interactive = true;
|
||||
|
|
@ -110,6 +113,8 @@ private:
|
|||
|
||||
// the total amount of distance scrolled
|
||||
QPointF m_totalScrollDelta;
|
||||
|
||||
QTimer m_touchpadScrollEndTimer;
|
||||
};
|
||||
|
||||
QML_DECLARE_TYPE(SwipeArea)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,15 @@ MobileShell.SwipeArea {
|
|||
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) {
|
||||
|
|
@ -54,12 +63,96 @@ MobileShell.SwipeArea {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
@ -67,9 +160,9 @@ MobileShell.SwipeArea {
|
|||
onSwipeEnded: endSwipe()
|
||||
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
|
||||
|
||||
onTouchpadScrollStarted: (point) => startSwipeWithPoint(point)
|
||||
onTouchpadScrollEnded: endSwipe()
|
||||
onTouchpadScrollMove: (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: {
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ MouseArea {
|
|||
// Virtual desktop pager (convergence mode, 2+ desktops)
|
||||
readonly property bool showPager: convergenceMode && virtualDesktopInfo.numberOfDesktops > 1
|
||||
property real pagerButtonWidth: showPager ? Math.min(root.height, Kirigami.Units.gridUnit * 2.5) : 0
|
||||
property int pagerWheelDelta: 0
|
||||
property bool pagerWheelLocked: false
|
||||
readonly property int pagerLeftCount: showPager ? Math.ceil(virtualDesktopInfo.numberOfDesktops / 2) : 0
|
||||
readonly property int pagerRightCount: showPager ? virtualDesktopInfo.numberOfDesktops - pagerLeftCount : 0
|
||||
property real desktopButtonWidth: convergenceMode ? root.height : 0
|
||||
|
|
@ -128,6 +130,15 @@ MouseArea {
|
|||
onTriggered: root.hideDockToolTip(root.activeDockToolTipItem)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pagerWheelEndTimer
|
||||
interval: 160
|
||||
onTriggered: {
|
||||
root.pagerWheelDelta = 0
|
||||
root.pagerWheelLocked = false
|
||||
}
|
||||
}
|
||||
|
||||
function requestDockToolTip(item) {
|
||||
activeDockToolTipItem = null
|
||||
pendingDockToolTipItem = item
|
||||
|
|
@ -192,6 +203,50 @@ MouseArea {
|
|||
return -1
|
||||
}
|
||||
|
||||
function activateAdjacentDesktop(direction) {
|
||||
let ids = virtualDesktopInfo.desktopIds
|
||||
if (!ids || ids.length <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
let currentIndex = root.desktopIndexForId(virtualDesktopInfo.currentDesktop)
|
||||
if (currentIndex < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let targetIndex = Math.max(0, Math.min(ids.length - 1, currentIndex + direction))
|
||||
if (targetIndex !== currentIndex) {
|
||||
root.folio.activateVirtualDesktop(String(ids[targetIndex]))
|
||||
}
|
||||
}
|
||||
|
||||
function handlePagerWheel(wheel) {
|
||||
if (!root.showPager) {
|
||||
return
|
||||
}
|
||||
|
||||
const axisDelta = wheel.angleDelta.y || -wheel.angleDelta.x
|
||||
if (axisDelta === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
pagerWheelEndTimer.restart()
|
||||
if (root.pagerWheelLocked) {
|
||||
return
|
||||
}
|
||||
|
||||
root.pagerWheelDelta += axisDelta * (wheel.inverted ? -1 : 1)
|
||||
if (root.pagerWheelDelta >= 120) {
|
||||
root.pagerWheelLocked = true
|
||||
root.pagerWheelDelta = 0
|
||||
root.activateAdjacentDesktop(-1)
|
||||
} else if (root.pagerWheelDelta <= -120) {
|
||||
root.pagerWheelLocked = true
|
||||
root.pagerWheelDelta = 0
|
||||
root.activateAdjacentDesktop(1)
|
||||
}
|
||||
}
|
||||
|
||||
function dynamicTilingMoveToDesktopAction(desktopId) {
|
||||
let index = root.desktopIndexForId(desktopId)
|
||||
if (index < 0) {
|
||||
|
|
@ -685,6 +740,7 @@ MouseArea {
|
|||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onWheel: (wheel) => root.handlePagerWheel(wheel)
|
||||
onClicked: (mouse) => {
|
||||
root.hideDockToolTip(leftDesktopBtn)
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
|
|
@ -794,6 +850,7 @@ MouseArea {
|
|||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onWheel: (wheel) => root.handlePagerWheel(wheel)
|
||||
onClicked: (mouse) => {
|
||||
root.hideDockToolTip(rightDesktopBtn)
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
|
|
|
|||
|
|
@ -188,9 +188,21 @@ Item {
|
|||
homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||
}
|
||||
|
||||
onTouchpadScrollStarted: homeScreenState.swipeStarted(0, 0);
|
||||
onTouchpadScrollEnded: homeScreenState.swipeEnded();
|
||||
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||
onTouchpadScrollStarted: {
|
||||
if (!ShellSettings.Settings.convergenceModeEnabled) {
|
||||
root.homeScreenState.swipeStarted(0, 0);
|
||||
}
|
||||
}
|
||||
onTouchpadScrollEnded: {
|
||||
if (!ShellSettings.Settings.convergenceModeEnabled) {
|
||||
root.homeScreenState.swipeEnded();
|
||||
}
|
||||
}
|
||||
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {
|
||||
if (!ShellSettings.Settings.convergenceModeEnabled) {
|
||||
root.homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ Item {
|
|||
value: drawer.visible
|
||||
}
|
||||
|
||||
TaskManager.VirtualDesktopInfo {
|
||||
id: virtualDesktopInfo
|
||||
}
|
||||
|
||||
//END API implementation
|
||||
|
||||
// Startup feedback fill animation
|
||||
|
|
@ -155,6 +159,7 @@ Item {
|
|||
MobileShell.ActionDrawerOpenSurface {
|
||||
id: swipeArea
|
||||
actionDrawer: drawer.actionDrawer
|
||||
virtualDesktopInfo: virtualDesktopInfo
|
||||
anchors.fill: parent
|
||||
|
||||
readonly property alias drawerVisible: drawer.visible
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ taskpanel="$repo_root/containments/taskpanel/qml/main.qml"
|
|||
folio_main="$repo_root/containments/homescreens/folio/qml/main.qml"
|
||||
shellutil_header="$repo_root/components/mobileshell/shellutil.h"
|
||||
shellutil_cpp="$repo_root/components/mobileshell/shellutil.cpp"
|
||||
swipearea_header="$repo_root/components/mobileshell/components/swipearea.h"
|
||||
swipearea_cpp="$repo_root/components/mobileshell/components/swipearea.cpp"
|
||||
folio_home="$repo_root/containments/homescreens/folio/qml/FolioHomeScreen.qml"
|
||||
favourites_bar="$repo_root/containments/homescreens/folio/qml/FavouritesBar.qml"
|
||||
folio_backend="$repo_root/containments/homescreens/folio/homescreen.h"
|
||||
folio_backend_cpp="$repo_root/containments/homescreens/folio/homescreen.cpp"
|
||||
action_content="$repo_root/components/mobileshell/qml/actiondrawer/private/ContentContainer.qml"
|
||||
|
|
@ -60,11 +63,34 @@ require_line "$panel" "visible: !ShellSettings.Settings.gamingModeEnabled"
|
|||
|
||||
require_line "$status_panel" "&& !ShellSettings.Settings.convergenceModeEnabled"
|
||||
require_line "$status_panel" "visible: !ShellSettings.Settings.convergenceModeEnabled"
|
||||
require_line "$status_panel" "TaskManager.VirtualDesktopInfo {"
|
||||
require_line "$status_panel" "MobileShell.ActionDrawerOpenSurface {"
|
||||
require_line "$status_panel" "virtualDesktopInfo: virtualDesktopInfo"
|
||||
require_line "$action_open_surface" "property var virtualDesktopInfo: null"
|
||||
require_line "$action_open_surface" "touchpadWorkspaceSwitchThreshold"
|
||||
require_line "$action_open_surface" "function activateAdjacentWorkspace(direction)"
|
||||
require_line "$action_open_surface" "virtualDesktopInfo.requestActivate(virtualDesktopInfo.desktopIds[targetIndex]);"
|
||||
require_line "$action_open_surface" "absY >= touchpadDirectionLockThreshold && absY > absX * touchpadAxisDominance"
|
||||
require_line "$action_open_surface" "workspaceScrollAvailable() && absX >= touchpadDirectionLockThreshold && absX > absY * touchpadAxisDominance"
|
||||
require_line "$action_open_surface" "MouseArea {"
|
||||
require_line "$action_open_surface" "acceptedButtons: Qt.NoButton"
|
||||
require_line "$action_open_surface" "hoverEnabled: true"
|
||||
require_line "$action_open_surface" "scrollGestureEnabled: false"
|
||||
require_line "$action_open_surface" "cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor"
|
||||
require_line "$swipearea_cpp" "const qreal WHEEL_STEP_PIXEL_DELTA = 48.0;"
|
||||
require_line "$swipearea_cpp" "const int TOUCHPAD_SCROLL_END_TIMEOUT = 160;"
|
||||
require_line "$swipearea_cpp" "const bool isTouchpad = event->deviceType() == QInputDevice::DeviceType::TouchPad;"
|
||||
require_line "$swipearea_cpp" "startTouchpadScroll(event->points().first().position());"
|
||||
require_line "$swipearea_cpp" "m_touchpadScrollEndTimer.start();"
|
||||
require_line "$swipearea_cpp" "if (scrollDelta.isNull() && !event->angleDelta().isNull())"
|
||||
require_line "$swipearea_cpp" "QWheelEvent::DefaultDeltasPerStep * WHEEL_STEP_PIXEL_DELTA"
|
||||
require_line "$swipearea_header" "QTimer m_touchpadScrollEndTimer;"
|
||||
require_line "$favourites_bar" "function handlePagerWheel(wheel)"
|
||||
require_line "$favourites_bar" "onWheel: (wheel) => root.handlePagerWheel(wheel)"
|
||||
require_line "$favourites_bar" "root.activateAdjacentDesktop(1)"
|
||||
require_line "$folio_home" "onTouchpadScrollStarted: {"
|
||||
require_line "$folio_home" "onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {"
|
||||
require_line "$folio_home" "if (!ShellSettings.Settings.convergenceModeEnabled) {"
|
||||
|
||||
require_line "$status_bar_template" "panel.location = \"top\";"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue