mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-18 17:24:01 -04:00
notifications: add 2 finger swipe gesture to dismiss on popups
fixes #3716
This commit is contained in:
@@ -44,6 +44,9 @@ Singleton {
|
||||
readonly property var swipeDetachSpring: swipeSpring(800, 0.95)
|
||||
readonly property var swipeSnapSpring: swipeSpring(550, 0.6)
|
||||
readonly property var swipeAttachSpring: swipeSpring(850, 0.95)
|
||||
readonly property real swipeWheelEngageDistance: 12
|
||||
readonly property int swipeWheelSettleMs: 500
|
||||
readonly property int swipeFlingProjectionMs: 200
|
||||
readonly property int expandedLimit: 10
|
||||
readonly property int collapsedLines: compact ? 1 : 2
|
||||
readonly property real summarySize: SettingsData.notificationSummaryFontSize || Theme.fontSizeMedium
|
||||
|
||||
@@ -55,13 +55,14 @@ Item {
|
||||
motion.retarget(target);
|
||||
}
|
||||
|
||||
function release() {
|
||||
function release(velocity = 0) {
|
||||
if (group?.row !== root)
|
||||
return;
|
||||
if (Math.abs(group.translation) > width * NotificationMetrics.swipeThreshold) {
|
||||
const projected = group.translation + velocity * NotificationMetrics.swipeFlingProjectionMs / 1000;
|
||||
if (Math.abs(projected) > width * NotificationMetrics.swipeThreshold) {
|
||||
dismissing = true;
|
||||
motion.snapTo(motion.value);
|
||||
dismissAnimation.to = (group.translation > 0 ? 1 : -1) * (width + bleed);
|
||||
dismissAnimation.to = (projected > 0 ? 1 : -1) * (width + bleed);
|
||||
dismissAnimation.start();
|
||||
}
|
||||
group.end(root);
|
||||
@@ -142,4 +143,11 @@ Item {
|
||||
}
|
||||
onTranslationChanged: root.group?.drag(root, translation.x)
|
||||
}
|
||||
|
||||
NotificationSwipeWheel {
|
||||
enabled: root.group !== null && !root.dismissing
|
||||
onBegan: root.group.begin(root)
|
||||
onMoved: travel => root.group?.drag(root, travel)
|
||||
onEnded: velocity => root.release(velocity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import QtQuick
|
||||
import "../../DCommon/Common/WheelInput.js" as WheelInput
|
||||
import "../../DCommon/Widgets/ScrollConstants.js" as Scroll
|
||||
|
||||
WheelHandler {
|
||||
id: handler
|
||||
|
||||
property real travel: 0
|
||||
property real drift: 0
|
||||
property bool engaged: false
|
||||
property real lastTime: 0
|
||||
property var samples: []
|
||||
readonly property Timer settle: Timer {
|
||||
interval: NotificationMetrics.swipeWheelSettleMs
|
||||
onTriggered: handler.finish()
|
||||
}
|
||||
|
||||
signal began
|
||||
signal moved(real travel)
|
||||
signal ended(real velocity)
|
||||
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
orientation: Qt.Horizontal
|
||||
blocking: false
|
||||
target: null
|
||||
|
||||
function velocity() {
|
||||
if (samples.length < 2)
|
||||
return 0;
|
||||
const span = samples[samples.length - 1].time - samples[0].time;
|
||||
if (span <= 0)
|
||||
return 0;
|
||||
const total = samples.reduce((sum, s) => sum + s.delta, 0);
|
||||
return Math.max(-Scroll.maxMomentumVelocity, Math.min(Scroll.maxMomentumVelocity, total / span * 1000));
|
||||
}
|
||||
|
||||
function finish() {
|
||||
settle.stop();
|
||||
const lifted = Date.now() - lastTime < Scroll.velocitySampleWindowMs;
|
||||
const fling = lifted ? velocity() : 0;
|
||||
travel = 0;
|
||||
drift = 0;
|
||||
samples = [];
|
||||
if (!engaged)
|
||||
return;
|
||||
engaged = false;
|
||||
ended(fling);
|
||||
}
|
||||
|
||||
onWheel: event => {
|
||||
if (!WheelInput.isTouchpad(event))
|
||||
return;
|
||||
const now = Date.now();
|
||||
// Qt Wayland negates finger deltas; natural scrolling flips them back and reports inverted
|
||||
const delta = (event.inverted ? event.pixelDelta.x : -event.pixelDelta.x) * Scroll.touchpadSpeed;
|
||||
travel += delta;
|
||||
drift += event.pixelDelta.y * Scroll.touchpadSpeed;
|
||||
samples = samples.filter(s => now - s.time < Scroll.velocitySampleWindowMs).concat({
|
||||
"delta": delta,
|
||||
"time": now
|
||||
});
|
||||
lastTime = now;
|
||||
settle.restart();
|
||||
if (!engaged) {
|
||||
if (Math.abs(travel) < NotificationMetrics.swipeWheelEngageDistance || Math.abs(travel) <= Math.abs(drift))
|
||||
return;
|
||||
engaged = true;
|
||||
began();
|
||||
}
|
||||
moved(travel);
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active)
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -589,7 +589,8 @@ PanelWindow {
|
||||
readonly property real swipeTravelDistance: width
|
||||
readonly property real swipeFadeStartOffset: swipeTravelDistance * swipeFadeStartRatio
|
||||
readonly property real swipeFadeDistance: Math.max(1, swipeTravelDistance - swipeFadeStartOffset)
|
||||
readonly property bool swipeActive: swipeDragHandler.active
|
||||
property bool swipeWheelActive: false
|
||||
readonly property bool swipeActive: swipeDragHandler.active || swipeWheelActive
|
||||
property bool swipeDismissing: false
|
||||
onSwipeDismissingChanged: {
|
||||
if (!win.connectedFrameMode)
|
||||
@@ -602,6 +603,19 @@ PanelWindow {
|
||||
win.popupChromeGeometryChanged();
|
||||
}
|
||||
|
||||
function releaseSwipe(velocity = 0) {
|
||||
if (win.exiting || swipeDismissing)
|
||||
return;
|
||||
const projected = swipeOffset + velocity * NotificationMetrics.swipeFlingProjectionMs / 1000;
|
||||
if (Math.abs(projected) <= dismissThreshold) {
|
||||
swipeOffset = 0;
|
||||
return;
|
||||
}
|
||||
swipeDismissDirection = projected < 0 ? -1 : 1;
|
||||
swipeDismissing = true;
|
||||
swipeDismissAnim.start();
|
||||
}
|
||||
|
||||
readonly property bool shadowsAllowed: win.popupWindowShadowActive
|
||||
readonly property var elevLevel: Theme.elevationLevel2
|
||||
readonly property real cardInset: Theme.snap(Theme.spacingXS, win.dpr)
|
||||
@@ -817,16 +831,9 @@ PanelWindow {
|
||||
yAxis.enabled: false
|
||||
|
||||
onActiveChanged: {
|
||||
if (active || win.exiting || content.swipeDismissing)
|
||||
if (active)
|
||||
return;
|
||||
|
||||
if (Math.abs(content.swipeOffset) > content.dismissThreshold) {
|
||||
content.swipeDismissDirection = content.swipeOffset < 0 ? -1 : 1;
|
||||
content.swipeDismissing = true;
|
||||
swipeDismissAnim.start();
|
||||
} else {
|
||||
content.swipeOffset = 0;
|
||||
}
|
||||
content.releaseSwipe();
|
||||
}
|
||||
|
||||
onTranslationChanged: {
|
||||
@@ -893,6 +900,27 @@ PanelWindow {
|
||||
]
|
||||
}
|
||||
|
||||
// Sits on the untranslated card footprint: inside content the handler would lose the pointer once the card slides past it,
|
||||
// and above content so it sees the gesture before DFlickable's blocking vertical handler
|
||||
Item {
|
||||
parent: slideClip
|
||||
x: content.x
|
||||
y: content.y
|
||||
width: content.width
|
||||
height: content.height
|
||||
visible: content.visible
|
||||
|
||||
NotificationSwipeWheel {
|
||||
enabled: !win.exiting && !content.swipeDismissing
|
||||
onBegan: content.swipeWheelActive = true
|
||||
onMoved: travel => content.swipeOffset = travel
|
||||
onEnded: velocity => {
|
||||
content.swipeWheelActive = false;
|
||||
content.releaseSwipe(velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DCommon.DAnim {
|
||||
id: enterAnimation
|
||||
target: win
|
||||
|
||||
Reference in New Issue
Block a user