1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-13 17:22:08 -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 viewModeChanged(string sectionId, string mode)
signal searchQueryRequested(string query) signal searchQueryRequested(string query)
onActiveChanged: {
if (!active) {
sections = [];
flatModel = [];
selectedItem = null;
}
}
Connections { Connections {
target: SettingsData target: SettingsData
function onSortAppsAlphabeticallyChanged() { function onSortAppsAlphabeticallyChanged() {

View File

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

View File

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

View File

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