swipearea: Add support for touchpad

This adds support for touchpad scrolling in various shell components, such as the lockscreen, homescreen and action drawer.

Currently TODO because it appears to be very buggy when there is a control underneath that also accepts touchpad input (ex. flickable). The touchpad scroll start appears to get called by Qt, but not the end event, so I am unable to "let go" of the flick. Not sure if it's a wayland issue.

This also appears to not work in the nested KWin session, not sure if it's because of libinput or something
This commit is contained in:
Devin Lin 2024-07-15 22:32:01 -04:00
parent cf915cae4a
commit 3adcd1d51c
6 changed files with 110 additions and 16 deletions

View file

@ -216,6 +216,52 @@ void SwipeArea::touchUngrabEvent()
QQuickItem::touchUngrabEvent();
}
void SwipeArea::wheelEvent(QWheelEvent *event)
{
if (!m_interactive) {
QQuickItem::wheelEvent(event);
return;
}
event->setAccepted(false);
switch (event->phase()) {
case Qt::ScrollBegin:
if (!m_touchpadScrolling) {
event->accept();
m_touchpadScrolling = true;
m_totalScrollDelta = QPointF{0, 0};
Q_EMIT touchpadScrollStarted(event->points().first().position());
}
break;
case Qt::ScrollEnd:
if (m_touchpadScrolling) {
m_touchpadScrolling = false;
m_totalScrollDelta = QPointF{0, 0};
Q_EMIT touchpadScrollEnded();
}
break;
default:
break;
}
// HACK: if it isn't the touchpad, we never get the isBeginEvent() and isEndEvent() events
if (!m_touchpadScrolling) {
return;
}
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());
event->accept();
}
void SwipeArea::setMoving(bool moving)
{
m_moving = moving;
@ -242,6 +288,8 @@ void SwipeArea::resetSwipe()
void SwipeArea::handlePressEvent(QPointerEvent *event, QPointF point)
{
Q_UNUSED(event);
// ignore more touch events
if (m_pressed) {
return;
@ -255,6 +303,9 @@ void SwipeArea::handlePressEvent(QPointerEvent *event, QPointF point)
void SwipeArea::handleReleaseEvent(QPointerEvent *event, QPointF point)
{
Q_UNUSED(event);
Q_UNUSED(point);
// if we are in a swipe
if (m_moving) {
Q_EMIT swipeEnded();
@ -265,6 +316,8 @@ void SwipeArea::handleReleaseEvent(QPointerEvent *event, QPointF point)
void SwipeArea::handleMoveEvent(QPointerEvent *event, QPointF point)
{
Q_UNUSED(event);
if (!m_stealMouse) {
if (!m_skipSwipeThreshold) {
// if we haven't reached the swipe registering threshold yet, don't start the swipe

View file

@ -58,6 +58,10 @@ Q_SIGNALS:
// totalDeltaX, totalDeltaY - amount move since startedSwipe()
void swipeMove(qreal totalDeltaX, qreal totalDeltaY, qreal deltaX, qreal deltaY);
void touchpadScrollStarted(QPointF point);
void touchpadScrollEnded();
void touchpadScrollMove(qreal totalDeltaX, qreal totalDeltaY, qreal deltaX, qreal deltaY);
protected:
bool childMouseEventFilter(QQuickItem *item, QEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
@ -66,6 +70,7 @@ protected:
void mouseUngrabEvent() override;
void touchEvent(QTouchEvent *event) override;
void touchUngrabEvent() override;
void wheelEvent(QWheelEvent *event) override;
private:
void setMoving(bool moving);
@ -82,6 +87,7 @@ private:
Mode m_mode = Mode::BothAxis;
bool m_interactive = true;
bool m_pressed = false;
bool m_touchpadScrolling = false;
// whether we have started a flick
bool m_moving = false;
@ -100,6 +106,9 @@ private:
// whether to skip trying to measure the swipe threshold
bool m_skipSwipeThreshold;
// the total amount of distance scrolled
QPointF m_totalScrollDelta;
};
QML_DECLARE_TYPE(SwipeArea)

View file

@ -267,18 +267,28 @@ Item {
mode: MobileShell.SwipeArea.VerticalOnly
anchors.fill: parent
onSwipeStarted: {
function startSwipe() {
root.cancelAnimations();
root.dragging = true;
}
onSwipeEnded: {
function endSwipe() {
root.dragging = false;
root.updateState();
}
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {
function moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) {
root.offset += deltaY;
}
onSwipeStarted: startSwipe()
onSwipeEnded: endSwipe()
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
onTouchpadScrollStarted: startSwipe()
onTouchpadScrollEnded: endSwipe()
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
Loader {
id: contentContainerLoader
anchors.fill: parent

View file

@ -33,6 +33,17 @@ MobileShell.SwipeArea {
actionDrawer.visible = true;
}
function startSwipeWithPoint(point) {
// if the user swiped from the top left, otherwise it's from the top right
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();
@ -44,16 +55,11 @@ MobileShell.SwipeArea {
anchors.fill: parent
onSwipeStarted: (point) => {
// if the user swiped from the top left, otherwise it's from the top right
if (point.x < root.width / 2) {
actionDrawer.openToPinnedMode = ShellSettings.Settings.actionDrawerTopLeftMode == ShellSettings.Settings.Pinned;
} else {
actionDrawer.openToPinnedMode = ShellSettings.Settings.actionDrawerTopRightMode == ShellSettings.Settings.Pinned;
}
startSwipe();
}
onSwipeStarted: (point) => startSwipeWithPoint(point)
onSwipeEnded: endSwipe()
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
onTouchpadScrollStarted: (point) => startSwipeWithPoint(point)
onTouchpadScrollEnded: endSwipe()
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
}

View file

@ -124,6 +124,10 @@ 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);
onPressedChanged: {
if (pressed) {
// ensures that components like the widget settings overlay close when swiping

View file

@ -71,16 +71,28 @@ MobileShell.SwipeArea {
}
}
onSwipeStarted: cancelAnimations();
onSwipeEnded: {
function __startSwipe() {
cancelAnimations();
}
function __endSwipe() {
if (!positionAnim.running) {
updateState();
}
}
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {
function __moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) {
position = Math.max(0, Math.min(keypadHeight, position - deltaY));
}
onSwipeStarted: __startSwipe()
onSwipeEnded: __endSwipe()
onSwipeMove: __moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
onTouchpadScrollStarted: __startSwipe()
onTouchpadScrollEnded: __endSwipe()
onTouchpadScrollMove: __moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
}