132 lines
4.2 KiB
QML
132 lines
4.2 KiB
QML
|
|
import Quickshell
|
||
|
|
import QtQuick
|
||
|
|
import Quickshell.Io
|
||
|
|
|
||
|
|
FloatingWindow {
|
||
|
|
id: gallery
|
||
|
|
|
||
|
|
required property var walColors
|
||
|
|
|
||
|
|
property color bgColor: (walColors.special && walColors.special.background) ? walColors.special.background : "#1d160d"
|
||
|
|
property color accentColor: (walColors.colors && walColors.colors.color1) ? walColors.colors.color1 : "#946F50"
|
||
|
|
|
||
|
|
title: "Photo Gallery"
|
||
|
|
color: "transparent"
|
||
|
|
implicitWidth: 300
|
||
|
|
implicitHeight: 600
|
||
|
|
|
||
|
|
required property string photosPath
|
||
|
|
property var photoFiles: []
|
||
|
|
property int currentIndex: -1
|
||
|
|
property int nextIndex: -1
|
||
|
|
|
||
|
|
// Slide direction: 0 = from left, 1 = from right
|
||
|
|
property int slideDirection: 0
|
||
|
|
|
||
|
|
// Track which image layer is on top
|
||
|
|
property bool showingFront: true
|
||
|
|
|
||
|
|
Process {
|
||
|
|
id: listProc
|
||
|
|
command: ["bash", "-c", "find " + gallery.photosPath + " -maxdepth 1 -type f \\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.webp' \\) | sort"]
|
||
|
|
running: true
|
||
|
|
|
||
|
|
stdout: StdioCollector {
|
||
|
|
onDataChanged: {
|
||
|
|
let lines = text.trim().split("\n").filter(l => l.length > 0)
|
||
|
|
gallery.photoFiles = lines
|
||
|
|
if (lines.length > 0) {
|
||
|
|
gallery.currentIndex = Math.floor(Math.random() * lines.length)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Timer {
|
||
|
|
id: cycleTimer
|
||
|
|
interval: 5000
|
||
|
|
running: gallery.photoFiles.length > 1
|
||
|
|
repeat: true
|
||
|
|
onTriggered: gallery.pickNext()
|
||
|
|
}
|
||
|
|
|
||
|
|
function pickNext() {
|
||
|
|
if (photoFiles.length < 2) return
|
||
|
|
|
||
|
|
let idx
|
||
|
|
do {
|
||
|
|
idx = Math.floor(Math.random() * photoFiles.length)
|
||
|
|
} while (idx === currentIndex)
|
||
|
|
|
||
|
|
nextIndex = idx
|
||
|
|
slideDirection = Math.floor(Math.random() * 4)
|
||
|
|
showingFront = !showingFront
|
||
|
|
|
||
|
|
let incoming = showingFront ? frontImage : backImage
|
||
|
|
let outgoing = showingFront ? backImage : frontImage
|
||
|
|
|
||
|
|
// Stop any running animations and reset positions
|
||
|
|
incoming.x = 0; incoming.y = 0
|
||
|
|
outgoing.x = 0; outgoing.y = 0
|
||
|
|
|
||
|
|
incoming.source = "file://" + photoFiles[nextIndex]
|
||
|
|
incoming.z = 1
|
||
|
|
outgoing.z = 0
|
||
|
|
|
||
|
|
let horizontal = slideDirection < 2
|
||
|
|
let size = horizontal ? implicitWidth : implicitHeight
|
||
|
|
let sign = (slideDirection === 0 || slideDirection === 3) ? -1 : 1
|
||
|
|
|
||
|
|
if (horizontal) {
|
||
|
|
incoming.x = sign * -size
|
||
|
|
incomingX.from = sign * -size; incomingX.to = 0
|
||
|
|
outgoingX.from = 0; outgoingX.to = sign * size
|
||
|
|
incomingX.target = incoming; outgoingX.target = outgoing
|
||
|
|
incomingY.target = null; outgoingY.target = null
|
||
|
|
incomingX.start(); outgoingX.start()
|
||
|
|
} else {
|
||
|
|
incoming.y = sign * -size
|
||
|
|
incomingY.from = sign * -size; incomingY.to = 0
|
||
|
|
outgoingY.from = 0; outgoingY.to = sign * size
|
||
|
|
incomingY.target = incoming; outgoingY.target = outgoing
|
||
|
|
incomingX.target = null; outgoingX.target = null
|
||
|
|
incomingY.start(); outgoingY.start()
|
||
|
|
}
|
||
|
|
|
||
|
|
currentIndex = nextIndex
|
||
|
|
}
|
||
|
|
|
||
|
|
NumberAnimation { id: incomingX; property: "x"; duration: 400; easing.type: Easing.InOutCubic }
|
||
|
|
NumberAnimation { id: outgoingX; property: "x"; duration: 400; easing.type: Easing.InOutCubic }
|
||
|
|
NumberAnimation { id: incomingY; property: "y"; duration: 400; easing.type: Easing.InOutCubic }
|
||
|
|
NumberAnimation { id: outgoingY; property: "y"; duration: 400; easing.type: Easing.InOutCubic }
|
||
|
|
|
||
|
|
Item {
|
||
|
|
anchors.fill: parent
|
||
|
|
clip: true
|
||
|
|
|
||
|
|
Image {
|
||
|
|
id: backImage
|
||
|
|
width: parent.width
|
||
|
|
height: parent.height
|
||
|
|
fillMode: Image.PreserveAspectCrop
|
||
|
|
asynchronous: true
|
||
|
|
mipmap: true
|
||
|
|
z: 0
|
||
|
|
}
|
||
|
|
|
||
|
|
Image {
|
||
|
|
id: frontImage
|
||
|
|
width: parent.width
|
||
|
|
height: parent.height
|
||
|
|
fillMode: Image.PreserveAspectCrop
|
||
|
|
asynchronous: true
|
||
|
|
mipmap: true
|
||
|
|
z: 1
|
||
|
|
source: gallery.currentIndex >= 0 && gallery.photoFiles.length > 0
|
||
|
|
? "file://" + gallery.photoFiles[gallery.currentIndex]
|
||
|
|
: ""
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|