noctalia-shell/Modules/Background/Background.qml

309 lines
9.8 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services
Variants {
id: backgroundVariants
model: Quickshell.screens
delegate: Loader {
required property ShellScreen modelData
active: Settings.isLoaded && modelData && Settings.data.wallpaper.enabled
sourceComponent: PanelWindow {
id: root
// Internal state management
property string transitionType: "fade"
property real transitionProgress: 0
readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness
readonly property var allTransitions: WallpaperService.allTransitions
readonly property bool transitioning: transitionAnimation.running
// Wipe direction: 0=left, 1=right, 2=up, 3=down
property real wipeDirection: 0
// Disc
property real discCenterX: 0.5
property real discCenterY: 0.5
// Stripe
property real stripesCount: 16
property real stripesAngle: 0
// Used to debounce wallpaper changes
property string futureWallpaper: ""
// Fillmode default is "crop"
property real fillMode: 1.0
// On startup assign wallpaper immediately
Component.onCompleted: {
fillMode = WallpaperService.getFillModeUniform()
var path = modelData ? WallpaperService.getWallpaper(modelData.name) : ""
setWallpaperImmediate(path)
}
Connections {
target: Settings.data.wallpaper
function onFillModeChanged() {
fillMode = WallpaperService.getFillModeUniform()
}
}
// External state management
Connections {
target: WallpaperService
function onWallpaperChanged(screenName, path) {
if (screenName === modelData.name) {
// Update wallpaper display
// Set wallpaper immediately on startup
futureWallpaper = path
debounceTimer.restart()
}
}
}
color: Color.transparent
screen: modelData
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "quickshell-wallpaper"
anchors {
bottom: true
top: true
right: true
left: true
}
Timer {
id: debounceTimer
interval: 333
running: false
repeat: false
onTriggered: {
changeWallpaper()
}
}
Image {
id: currentWallpaper
source: ""
smooth: true
mipmap: false
visible: false
cache: false
// currentWallpaper should not be asynchronous to avoid flickering when swapping next to current.
asynchronous: false
}
Image {
id: nextWallpaper
source: ""
smooth: true
mipmap: false
visible: false
cache: false
asynchronous: true
}
// Fade or None transition shader
ShaderEffect {
id: fadeShader
anchors.fill: parent
visible: transitionType === "fade" || transitionType === "none"
property variant source1: currentWallpaper
property variant source2: nextWallpaper
property real progress: root.transitionProgress
// Fill mode properties
property real fillMode: root.fillMode
property real imageWidth1: source1.sourceSize.width
property real imageHeight1: source1.sourceSize.height
property real imageWidth2: source2.sourceSize.width
property real imageHeight2: source2.sourceSize.height
property real screenWidth: width
property real screenHeight: height
property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_fade.frag.qsb")
}
// Wipe transition shader
ShaderEffect {
id: wipeShader
anchors.fill: parent
visible: transitionType === "wipe"
property variant source1: currentWallpaper
property variant source2: nextWallpaper
property real progress: root.transitionProgress
property real smoothness: root.edgeSmoothness
property real direction: root.wipeDirection
// Fill mode properties
property real fillMode: root.fillMode
property real imageWidth1: source1.sourceSize.width
property real imageHeight1: source1.sourceSize.height
property real imageWidth2: source2.sourceSize.width
property real imageHeight2: source2.sourceSize.height
property real screenWidth: width
property real screenHeight: height
property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_wipe.frag.qsb")
}
// Disc reveal transition shader
ShaderEffect {
id: discShader
anchors.fill: parent
visible: transitionType === "disc"
property variant source1: currentWallpaper
property variant source2: nextWallpaper
property real progress: root.transitionProgress
property real smoothness: root.edgeSmoothness
property real aspectRatio: root.width / root.height
property real centerX: root.discCenterX
property real centerY: root.discCenterY
// Fill mode properties
property real fillMode: root.fillMode
property real imageWidth1: source1.sourceSize.width
property real imageHeight1: source1.sourceSize.height
property real imageWidth2: source2.sourceSize.width
property real imageHeight2: source2.sourceSize.height
property real screenWidth: width
property real screenHeight: height
property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_disc.frag.qsb")
}
// Diagonal stripes transition shader
ShaderEffect {
id: stripesShader
anchors.fill: parent
visible: transitionType === "stripes"
property variant source1: currentWallpaper
property variant source2: nextWallpaper
property real progress: root.transitionProgress
property real smoothness: root.edgeSmoothness
property real aspectRatio: root.width / root.height
property real stripeCount: root.stripesCount
property real angle: root.stripesAngle
// Fill mode properties
property real fillMode: root.fillMode
property real imageWidth1: source1.sourceSize.width
property real imageHeight1: source1.sourceSize.height
property real imageWidth2: source2.sourceSize.width
property real imageHeight2: source2.sourceSize.height
property real screenWidth: width
property real screenHeight: height
property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_stripes.frag.qsb")
}
// Animation for the transition progress
NumberAnimation {
id: transitionAnimation
target: root
property: "transitionProgress"
from: 0.0
to: 1.0
// The stripes shader feels faster visually, we make it a bit slower here.
duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration
* 1.6 : Settings.data.wallpaper.transitionDuration
easing.type: Easing.InOutCubic
onFinished: {
// Swap images after transition completes
currentWallpaper.source = nextWallpaper.source
nextWallpaper.source = ""
transitionProgress = 0.0
}
}
function startTransition() {
if (!transitioning && nextWallpaper.source != currentWallpaper.source) {
transitionAnimation.start()
}
}
function setWallpaperImmediate(source) {
transitionAnimation.stop()
transitionProgress = 0.0
currentWallpaper.source = source
nextWallpaper.source = ""
}
function setWallpaperWithTransition(source) {
if (source != currentWallpaper.source) {
if (transitioning) {
// We are interrupting a transition
transitionAnimation.stop()
transitionProgress = 0
currentWallpaper.source = nextWallpaper.source
nextWallpaper.source = ""
}
nextWallpaper.source = source
startTransition()
}
}
// Main method that actually trigger the wallpaper change
function changeWallpaper() {
// Get the transitionType from the settings
transitionType = Settings.data.wallpaper.transitionType
if (transitionType == "random") {
var index = Math.floor(Math.random() * allTransitions.length)
transitionType = allTransitions[index]
}
// Ensure the transition type really exists
if (transitionType !== "none" && !allTransitions.includes(transitionType)) {
transitionType = "fade"
}
//Logger.log("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType)
switch (transitionType) {
case "none":
setWallpaperImmediate(futureWallpaper)
break
case "wipe":
wipeDirection = Math.random() * 4
setWallpaperWithTransition(futureWallpaper)
break
case "disc":
discCenterX = Math.random()
discCenterY = Math.random()
setWallpaperWithTransition(futureWallpaper)
break
case "stripes":
stripesCount = Math.round(Math.random() * 20 + 4)
stripesAngle = Math.random() * 360
setWallpaperWithTransition(futureWallpaper)
break
default:
setWallpaperWithTransition(futureWallpaper)
break
}
}
}
}
}