1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-03 20:32:07 -04:00

scrollies: switch to frame animation for kinetic scroll

This commit is contained in:
bbedward
2026-02-13 22:26:15 -05:00
parent bb8e0d384f
commit 124106de87
5 changed files with 97 additions and 93 deletions

View File

@@ -42,6 +42,14 @@ Item {
signal viewModeChanged(string sectionId, string mode)
signal searchQueryRequested(string query)
onActiveChanged: {
if (!active) {
sections = [];
flatModel = [];
selectedItem = null;
}
}
Connections {
target: SettingsData
function onSortAppsAlphabeticallyChanged() {

View File

@@ -1,21 +1,20 @@
import QtQuick
import QtQuick.Controls
import qs.Widgets
import "ScrollConstants.js" as Scroll
Flickable {
id: flickable
property alias verticalScrollBar: vbar
property real mouseWheelSpeed: 60
property real mouseWheelSpeed: Scroll.mouseWheelSpeed
property real momentumVelocity: 0
property bool isMomentumActive: false
property real friction: 0.95
property real minMomentumVelocity: 50
property real maxMomentumVelocity: 2500
property real friction: Scroll.friction
property bool _scrollBarActive: false
flickDeceleration: 1500
maximumFlickVelocity: 2000
flickDeceleration: Scroll.flickDeceleration
maximumFlickVelocity: Scroll.maximumFlickVelocity
boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.FollowBoundsBehavior
pressDelay: 0
@@ -24,8 +23,8 @@ Flickable {
WheelHandler {
id: wheelHandler
property real touchpadSpeed: 2.8
property real momentumRetention: 0.92
property real touchpadSpeed: Scroll.touchpadSpeed
property real momentumRetention: Scroll.momentumRetention
property real lastWheelTime: 0
property real momentum: 0
property var velocitySamples: []
@@ -33,7 +32,7 @@ Flickable {
function startMomentum() {
flickable.isMomentumActive = true;
momentumTimer.start();
momentumAnim.running = true;
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
@@ -54,7 +53,7 @@ Flickable {
if (isTraditionalMouse) {
sessionUsedMouseWheel = true;
momentumTimer.stop();
momentumAnim.running = false;
flickable.isMomentumActive = false;
velocitySamples = [];
momentum = 0;
@@ -72,7 +71,7 @@ Flickable {
flickable.contentY = newY;
} else if (isHighDpiMouse) {
sessionUsedMouseWheel = true;
momentumTimer.stop();
momentumAnim.running = false;
flickable.isMomentumActive = false;
velocitySamples = [];
momentum = 0;
@@ -89,7 +88,7 @@ Flickable {
flickable.contentY = newY;
} else if (isTouchpad) {
sessionUsedMouseWheel = false;
momentumTimer.stop();
momentumAnim.running = false;
flickable.isMomentumActive = false;
let delta = event.pixelDelta.y * touchpadSpeed;
@@ -98,18 +97,18 @@ Flickable {
"delta": delta,
"time": currentTime
});
velocitySamples = velocitySamples.filter(s => currentTime - s.time < 100);
velocitySamples = velocitySamples.filter(s => currentTime - s.time < Scroll.velocitySampleWindowMs);
if (velocitySamples.length > 1) {
const totalDelta = velocitySamples.reduce((sum, s) => sum + s.delta, 0);
const timeSpan = currentTime - velocitySamples[0].time;
if (timeSpan > 0) {
flickable.momentumVelocity = Math.max(-flickable.maxMomentumVelocity, Math.min(flickable.maxMomentumVelocity, totalDelta / timeSpan * 1000));
flickable.momentumVelocity = Math.max(-Scroll.maxMomentumVelocity, Math.min(Scroll.maxMomentumVelocity, totalDelta / timeSpan * 1000));
}
}
if (timeDelta < 50) {
momentum = momentum * momentumRetention + delta * 0.15;
if (timeDelta < Scroll.momentumTimeThreshold) {
momentum = momentum * momentumRetention + delta * Scroll.momentumDeltaFactor;
delta += momentum;
} else {
momentum = 0;
@@ -130,7 +129,7 @@ Flickable {
onActiveChanged: {
if (!active) {
if (!sessionUsedMouseWheel && Math.abs(flickable.momentumVelocity) >= flickable.minMomentumVelocity) {
if (!sessionUsedMouseWheel && Math.abs(flickable.momentumVelocity) >= Scroll.minMomentumVelocity) {
startMomentum();
} else {
velocitySamples = [];
@@ -146,42 +145,34 @@ Flickable {
}
onMovementEnded: vbar.hideTimer.restart()
Timer {
id: momentumTimer
interval: 16
repeat: true
FrameAnimation {
id: momentumAnim
running: false
onTriggered: {
const newY = flickable.contentY - flickable.momentumVelocity * 0.016;
const dt = frameTime;
const newY = flickable.contentY - flickable.momentumVelocity * dt;
const maxY = Math.max(0, flickable.contentHeight - flickable.height);
if (newY < 0 || newY > maxY) {
flickable.contentY = newY < 0 ? 0 : maxY;
stop();
running = false;
flickable.isMomentumActive = false;
flickable.momentumVelocity = 0;
return;
}
flickable.contentY = newY;
flickable.momentumVelocity *= flickable.friction;
flickable.momentumVelocity *= Math.pow(flickable.friction, dt / 0.016);
if (Math.abs(flickable.momentumVelocity) < 5) {
stop();
if (Math.abs(flickable.momentumVelocity) < Scroll.momentumStopThreshold) {
running = false;
flickable.isMomentumActive = false;
flickable.momentumVelocity = 0;
}
}
}
NumberAnimation {
id: returnToBoundsAnimation
target: flickable
property: "contentY"
duration: 300
easing.type: Easing.OutQuad
}
ScrollBar.vertical: DankScrollbar {
id: vbar
}

View File

@@ -1,18 +1,17 @@
import QtQuick
import QtQuick.Controls
import qs.Widgets
import "ScrollConstants.js" as Scroll
GridView {
id: gridView
property real momentumVelocity: 0
property bool isMomentumActive: false
property real friction: 0.95
property real minMomentumVelocity: 50
property real maxMomentumVelocity: 2500
property real friction: Scroll.friction
flickDeceleration: 1500
maximumFlickVelocity: 2000
flickDeceleration: Scroll.flickDeceleration
maximumFlickVelocity: Scroll.maximumFlickVelocity
boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.FollowBoundsBehavior
pressDelay: 0
@@ -27,9 +26,9 @@ GridView {
WheelHandler {
id: wheelHandler
property real mouseWheelSpeed: 60
property real touchpadSpeed: 2.8
property real momentumRetention: 0.92
property real mouseWheelSpeed: Scroll.mouseWheelSpeed
property real touchpadSpeed: Scroll.touchpadSpeed
property real momentumRetention: Scroll.momentumRetention
property real lastWheelTime: 0
property real momentum: 0
property var velocitySamples: []
@@ -37,7 +36,7 @@ GridView {
function startMomentum() {
isMomentumActive = true;
momentumTimer.start();
momentumAnim.running = true;
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
@@ -57,7 +56,7 @@ GridView {
if (isTraditionalMouse) {
sessionUsedMouseWheel = true;
momentumTimer.stop();
momentumAnim.running = false;
isMomentumActive = false;
velocitySamples = [];
momentum = 0;
@@ -75,7 +74,7 @@ GridView {
contentY = newY;
} else if (isHighDpiMouse) {
sessionUsedMouseWheel = true;
momentumTimer.stop();
momentumAnim.running = false;
isMomentumActive = false;
velocitySamples = [];
momentum = 0;
@@ -92,7 +91,7 @@ GridView {
contentY = newY;
} else if (isTouchpad) {
sessionUsedMouseWheel = false;
momentumTimer.stop();
momentumAnim.running = false;
isMomentumActive = false;
let delta = event.pixelDelta.y * touchpadSpeed;
@@ -101,18 +100,18 @@ GridView {
"delta": delta,
"time": currentTime
});
velocitySamples = velocitySamples.filter(s => currentTime - s.time < 100);
velocitySamples = velocitySamples.filter(s => currentTime - s.time < Scroll.velocitySampleWindowMs);
if (velocitySamples.length > 1) {
const totalDelta = velocitySamples.reduce((sum, s) => sum + s.delta, 0);
const timeSpan = currentTime - velocitySamples[0].time;
if (timeSpan > 0) {
momentumVelocity = Math.max(-maxMomentumVelocity, Math.min(maxMomentumVelocity, totalDelta / timeSpan * 1000));
momentumVelocity = Math.max(-Scroll.maxMomentumVelocity, Math.min(Scroll.maxMomentumVelocity, totalDelta / timeSpan * 1000));
}
}
if (timeDelta < 50) {
momentum = momentum * momentumRetention + delta * 0.15;
if (timeDelta < Scroll.momentumTimeThreshold) {
momentum = momentum * momentumRetention + delta * Scroll.momentumDeltaFactor;
delta += momentum;
} else {
momentum = 0;
@@ -132,7 +131,7 @@ GridView {
}
onActiveChanged: {
if (!active) {
if (!sessionUsedMouseWheel && Math.abs(momentumVelocity) >= minMomentumVelocity) {
if (!sessionUsedMouseWheel && Math.abs(momentumVelocity) >= Scroll.minMomentumVelocity) {
startMomentum();
} else {
velocitySamples = [];
@@ -142,41 +141,34 @@ GridView {
}
}
Timer {
id: momentumTimer
interval: 16
repeat: true
FrameAnimation {
id: momentumAnim
running: false
onTriggered: {
const newY = contentY - momentumVelocity * 0.016;
const dt = frameTime;
const newY = contentY - momentumVelocity * dt;
const maxY = Math.max(0, contentHeight - height);
if (newY < 0 || newY > maxY) {
contentY = newY < 0 ? 0 : maxY;
stop();
running = false;
isMomentumActive = false;
momentumVelocity = 0;
return;
}
contentY = newY;
momentumVelocity *= friction;
momentumVelocity *= Math.pow(friction, dt / 0.016);
if (Math.abs(momentumVelocity) < 5) {
stop();
if (Math.abs(momentumVelocity) < Scroll.momentumStopThreshold) {
running = false;
isMomentumActive = false;
momentumVelocity = 0;
}
}
}
NumberAnimation {
id: returnToBoundsAnimation
target: gridView
property: "contentY"
duration: 300
easing.type: Easing.OutQuad
}
ScrollBar.vertical: DankScrollbar {
id: vbar
}

View File

@@ -2,23 +2,22 @@ import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Widgets
import "ScrollConstants.js" as Scroll
ListView {
id: listView
property real scrollBarTopMargin: 0
property real mouseWheelSpeed: 60
property real mouseWheelSpeed: Scroll.mouseWheelSpeed
property real savedY: 0
property bool justChanged: false
property bool isUserScrolling: false
property real momentumVelocity: 0
property bool isMomentumActive: false
property real friction: 0.95
property real minMomentumVelocity: 50
property real maxMomentumVelocity: 2500
property real friction: Scroll.friction
flickDeceleration: 1500
maximumFlickVelocity: 2000
flickDeceleration: Scroll.flickDeceleration
maximumFlickVelocity: Scroll.maximumFlickVelocity
boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.FollowBoundsBehavior
pressDelay: 0
@@ -53,7 +52,7 @@ ListView {
WheelHandler {
id: wheelHandler
property real touchpadSpeed: 2.8
property real touchpadSpeed: Scroll.touchpadSpeed
property real lastWheelTime: 0
property real momentum: 0
property var velocitySamples: []
@@ -61,7 +60,7 @@ ListView {
function startMomentum() {
isMomentumActive = true;
momentumTimer.start();
momentumAnim.running = true;
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
@@ -83,7 +82,7 @@ ListView {
if (isTraditionalMouse) {
sessionUsedMouseWheel = true;
momentumTimer.stop();
momentumAnim.running = false;
isMomentumActive = false;
velocitySamples = [];
momentum = 0;
@@ -103,7 +102,7 @@ ListView {
savedY = newY;
} else if (isHighDpiMouse) {
sessionUsedMouseWheel = true;
momentumTimer.stop();
momentumAnim.running = false;
isMomentumActive = false;
velocitySamples = [];
momentum = 0;
@@ -122,7 +121,7 @@ ListView {
savedY = newY;
} else if (isTouchpad) {
sessionUsedMouseWheel = false;
momentumTimer.stop();
momentumAnim.running = false;
isMomentumActive = false;
let delta = event.pixelDelta.y * touchpadSpeed;
@@ -131,18 +130,18 @@ ListView {
"delta": delta,
"time": currentTime
});
velocitySamples = velocitySamples.filter(s => currentTime - s.time < 100);
velocitySamples = velocitySamples.filter(s => currentTime - s.time < Scroll.velocitySampleWindowMs);
if (velocitySamples.length > 1) {
const totalDelta = velocitySamples.reduce((sum, s) => sum + s.delta, 0);
const timeSpan = currentTime - velocitySamples[0].time;
if (timeSpan > 0) {
momentumVelocity = Math.max(-maxMomentumVelocity, Math.min(maxMomentumVelocity, totalDelta / timeSpan * 1000));
momentumVelocity = Math.max(-Scroll.maxMomentumVelocity, Math.min(Scroll.maxMomentumVelocity, totalDelta / timeSpan * 1000));
}
}
if (timeDelta < 50) {
momentum = momentum * 0.92 + delta * 0.15;
if (timeDelta < Scroll.momentumTimeThreshold) {
momentum = momentum * Scroll.momentumRetention + delta * Scroll.momentumDeltaFactor;
delta += momentum;
} else {
momentum = 0;
@@ -166,7 +165,7 @@ ListView {
onActiveChanged: {
if (!active) {
isUserScrolling = false;
if (!sessionUsedMouseWheel && Math.abs(momentumVelocity) >= minMomentumVelocity) {
if (!sessionUsedMouseWheel && Math.abs(momentumVelocity) >= Scroll.minMomentumVelocity) {
startMomentum();
} else {
velocitySamples = [];
@@ -176,20 +175,20 @@ ListView {
}
}
Timer {
id: momentumTimer
interval: 16
repeat: true
FrameAnimation {
id: momentumAnim
running: false
onTriggered: {
const newY = contentY - momentumVelocity * 0.016;
const dt = frameTime;
const newY = contentY - momentumVelocity * dt;
const maxY = Math.max(0, contentHeight - height + originY);
const minY = originY;
if (newY < minY || newY > maxY) {
contentY = newY < minY ? minY : maxY;
savedY = contentY;
stop();
running = false;
isMomentumActive = false;
momentumVelocity = 0;
return;
@@ -197,10 +196,10 @@ ListView {
contentY = newY;
savedY = newY;
momentumVelocity *= friction;
momentumVelocity *= Math.pow(friction, dt / 0.016);
if (Math.abs(momentumVelocity) < 5) {
stop();
if (Math.abs(momentumVelocity) < Scroll.momentumStopThreshold) {
running = false;
isMomentumActive = false;
momentumVelocity = 0;
}

View File

@@ -0,0 +1,14 @@
.pragma library
const friction = 0.96;
const touchpadSpeed = 3.5;
const mouseWheelSpeed = 60;
const momentumRetention = 0.92;
const momentumDeltaFactor = 0.15;
const maxMomentumVelocity = 2500;
const minMomentumVelocity = 50;
const momentumStopThreshold = 5;
const velocitySampleWindowMs = 100;
const momentumTimeThreshold = 50;
const flickDeceleration = 1500;
const maximumFlickVelocity = 2000;