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] : "" } } }