sidebar: anime: pull to load more

This commit is contained in:
end-4
2025-10-29 10:24:11 +01:00
parent 26361718a7
commit ede90eb282
2 changed files with 124 additions and 33 deletions
@@ -0,0 +1,90 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
Rectangle {
id: root
property bool loading: true
property double pullProgress: 0
// Size, color
property double implicitSize: 48
implicitWidth: implicitSize
implicitHeight: implicitSize
radius: Math.min(width, height) / 2
color: Appearance.colors.colPrimaryContainer
property double baseShapeSize: root.implicitSize * 1.3
property double leapZoomSize: root.baseShapeSize * 1.3
property double leapZoomProgress: 0
// Shape
property list<var> shapes: [
MaterialShape.Shape.SoftBurst,
MaterialShape.Shape.Cookie9Sided,
MaterialShape.Shape.Pentagon,
MaterialShape.Shape.Pill,
MaterialShape.Shape.Sunny,
MaterialShape.Shape.Cookie4Sided,
MaterialShape.Shape.Oval,
]
property int shapeIndex: 0
property double pullRotation: root.loading ? 0 : -(root.pullProgress * 360)
property double continuousRotation: 0
property double leapRotation: 0
rotation: pullRotation + continuousRotation + leapRotation
RotationAnimation on continuousRotation {
running: root.loading
duration: 12000
easing.type: Easing.Linear
loops: Animation.Infinite
from: 0
to: 360
}
Timer {
interval: 800
running: root.loading
repeat: true
onTriggered: leapAnimation.start()
}
ParallelAnimation {
id: leapAnimation
PropertyAction { target: root; property: "shapeIndex"; value: (root.shapeIndex + 1) % root.shapes.length }
RotationAnimation {
target: root
direction: RotationAnimation.Shortest
property: "leapRotation"
to: (root.leapRotation + 90) % 360
duration: 350
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: root
property: "leapZoomProgress"
from: 0
to: 1
duration: 750
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.standard
}
}
MaterialShape {
id: shape
anchors.centerIn: parent
shape: root.shapes[root.shapeIndex]
implicitSize: {
const leapZoomDiff = root.leapZoomSize - root.baseShapeSize
const progressFirstHalf = Math.min(root.leapZoomProgress, 0.5) * 2;
const progressSecondHalf = Math.max(root.leapZoomProgress - 0.5, 0) * 2;
print("progress", root.leapZoomProgress, "zoom", root.baseShapeSize + leapZoomDiff * progressFirstHalf - leapZoomDiff * progressSecondHalf)
return root.baseShapeSize + leapZoomDiff * progressFirstHalf - leapZoomDiff * progressSecondHalf;
}
color: Appearance.colors.colOnPrimaryContainer
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
@@ -23,12 +23,21 @@ Item {
property var suggestionQuery: ""
property var suggestionList: []
property bool pullLoading: false
property int pullLoadingGap: 80
property real normalizedPullDistance: Math.max(0, (1 - Math.exp(-booruResponseListView.verticalOvershoot / 50)))
Connections {
target: Booru
function onTagSuggestion(query, suggestions) {
root.suggestionQuery = query;
root.suggestionList = suggestions;
}
function onRunningRequestsChanged() {
if (Booru.runningRequests === 0) {
root.pullLoading = false;
}
}
}
property var allCommands: [
@@ -53,6 +62,8 @@ Item {
if (root.responses.length > 0) {
const lastResponse = root.responses[root.responses.length - 1];
root.handleInput(`${lastResponse.tags.join(" ")} ${parseInt(lastResponse.page) + 1}`);
} else {
root.handleInput("");
}
}
},
@@ -176,6 +187,14 @@ Item {
downloadPath: root.downloadPath
nsfwPath: root.nsfwPath
}
onDragEnded: { // Pull to load more
const gap = booruResponseListView.verticalOvershoot
if (gap > root.pullLoadingGap) {
root.pullLoading = true
root.handleInput(`${root.commandPrefix}next`)
}
}
}
PagePlaceholder {
@@ -192,42 +211,24 @@ Item {
target: booruResponseListView
}
Item { // Queries awaiting response
MaterialLoadingIndicator {
id: loadingIndicator
z: 4
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 10
implicitHeight: pendingBackground.implicitHeight
opacity: Booru.runningRequests > 0 ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Rectangle {
id: pendingBackground
color: Appearance.m3colors.m3inverseSurface
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitHeight: pendingText.implicitHeight + 12 * 2
radius: Appearance.rounding.verysmall
StyledText {
id: pendingText
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.m3colors.m3inverseOnSurface
wrapMode: Text.Wrap
text: Translation.tr("%1 queries pending").arg(Booru.runningRequests)
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 20 + (root.pullLoading ? 0 : Math.max(0, (root.normalizedPullDistance - 0.5) * 36))
Behavior on bottomMargin {
NumberAnimation {
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
}
loading: root.pullLoading || Booru.runningRequests > 0
pullProgress: Math.min(1, booruResponseListView.verticalOvershoot / root.pullLoadingGap)
scale: root.pullLoading ? 1 : Math.min(1, root.normalizedPullDistance * 2)
}
}