diff options
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/WallpaperBackground.qml')
| -rw-r--r-- | raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/WallpaperBackground.qml | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/WallpaperBackground.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/WallpaperBackground.qml new file mode 100644 index 0000000..8430b01 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/WallpaperBackground.qml @@ -0,0 +1,658 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import Quickshell.Wayland +import qs.Common +import qs.Widgets +import qs.Services + +Variants { + model: { + if (SessionData.isGreeterMode) { + return Quickshell.screens; + } + return SettingsData.getFilteredScreens("wallpaper"); + } + + PanelWindow { + id: wallpaperWindow + + required property var modelData + + screen: modelData + + WlrLayershell.layer: WlrLayer.Background + WlrLayershell.exclusionMode: ExclusionMode.Ignore + + anchors.top: true + anchors.bottom: true + anchors.left: true + anchors.right: true + + color: "transparent" + + mask: Region { + item: Item {} + } + + Item { + id: root + anchors.fill: parent + + function encodeFileUrl(path) { + if (!path) + return ""; + return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/'); + } + + property string source: SessionData.getMonitorWallpaper(modelData.name) || "" + property bool isColorSource: source.startsWith("#") + property string transitionType: SessionData.wallpaperTransition + property string actualTransitionType: transitionType + property bool isInitialized: false + + Connections { + target: SessionData + function onIsLightModeChanged() { + if (SessionData.perModeWallpaper) { + var newSource = SessionData.getMonitorWallpaper(modelData.name) || ""; + if (newSource !== root.source) { + root.source = newSource; + } + } + } + } + onTransitionTypeChanged: { + if (transitionType !== "random") { + actualTransitionType = transitionType; + return; + } + actualTransitionType = SessionData.includedTransitions.length === 0 ? "none" : SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)]; + } + + property real transitionProgress: 0 + property real shaderFillMode: getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name)) + property vector4d fillColor: Qt.vector4d(0, 0, 0, 1) + property real edgeSmoothness: 0.1 + + property real wipeDirection: 0 + property real discCenterX: 0.5 + property real discCenterY: 0.5 + property real stripesCount: 16 + property real stripesAngle: 0 + + readonly property bool transitioning: transitionAnimation.running + property bool effectActive: false + property bool _renderSettling: true + property bool _overviewBlurSettling: false + property bool useNextForEffect: false + property string pendingWallpaper: "" + property string _deferredSource: "" + readonly property bool overviewBlurActive: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== "" + + Connections { + target: currentWallpaper + function onStatusChanged() { + if (currentWallpaper.status !== Image.Ready && currentWallpaper.status !== Image.Error) + return; + root._renderSettling = true; + renderSettleTimer.restart(); + } + } + + function _recheckScreenScale() { + const newScale = CompositorService.getScreenScale(modelData); + if (newScale !== root.screenScale) { + console.info("WallpaperBackground: screen scale corrected for", modelData.name + ":", root.screenScale, "->", newScale); + root.screenScale = newScale; + } + } + + Connections { + target: wallpaperWindow + function onWidthChanged() { + root._renderSettling = true; + renderSettleTimer.restart(); + } + function onHeightChanged() { + root._renderSettling = true; + renderSettleTimer.restart(); + } + } + + Connections { + target: Quickshell + function onScreensChanged() { + root._renderSettling = true; + renderSettleTimer.restart(); + } + } + + Connections { + target: NiriService + function onDisplayScalesChanged() { + root._recheckScreenScale(); + root._renderSettling = true; + renderSettleTimer.restart(); + } + } + + Connections { + target: WlrOutputService + function onWlrOutputAvailableChanged() { + root._recheckScreenScale(); + root._renderSettling = true; + renderSettleTimer.restart(); + } + } + + Connections { + target: NiriService + function onInOverviewChanged() { + root._overviewBlurSettling = true; + overviewBlurSettleTimer.restart(); + } + } + + Connections { + target: SettingsData + function onBlurWallpaperOnOverviewChanged() { + root._overviewBlurSettling = true; + overviewBlurSettleTimer.restart(); + } + } + + Connections { + target: CompositorService + function onRandrDataReady() { + if (root._deferredSource) { + const src = root._deferredSource; + root._deferredSource = ""; + root.setWallpaperImmediate(src); + } else { + root._recheckScreenScale(); + } + } + } + + Connections { + target: IdleService + function onIsShellLockedChanged() { + if (!IdleService.isShellLocked) { + root._renderSettling = true; + renderSettleTimer.restart(); + } + } + } + + Timer { + id: renderSettleTimer + interval: 1000 + onTriggered: root._renderSettling = false + } + + Timer { + id: overviewBlurSettleTimer + interval: 150 + onTriggered: root._overviewBlurSettling = false + } + + function getFillMode(modeName) { + switch (modeName) { + case "Stretch": + return Image.Stretch; + case "Fit": + case "PreserveAspectFit": + return Image.PreserveAspectFit; + case "Fill": + case "PreserveAspectCrop": + return Image.PreserveAspectCrop; + case "Tile": + return Image.Tile; + case "TileVertically": + return Image.TileVertically; + case "TileHorizontally": + return Image.TileHorizontally; + case "Pad": + return Image.Pad; + default: + return Image.PreserveAspectCrop; + } + } + + Component.onCompleted: { + if (typeof wallpaperWindow.updatesEnabled !== "undefined") + wallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || root.overviewBlurActive || root._overviewBlurSettling || root.pendingWallpaper !== "" || root._deferredSource !== "" || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading); + + if (!source) { + root._renderSettling = false; + } + isInitialized = true; + } + + onSourceChanged: { + if (!source || source.startsWith("#")) { + setWallpaperImmediate(""); + return; + } + + const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source); + + if (!isInitialized || !currentWallpaper.source) { + if (!CompositorService.randrReady) { + _deferredSource = formattedSource; + return; + } + setWallpaperImmediate(formattedSource); + return; + } + if (CompositorService.isNiri && SessionData.isSwitchingMode) { + setWallpaperImmediate(formattedSource); + return; + } + changeWallpaper(formattedSource); + } + + function setWallpaperImmediate(newSource) { + transitionAnimation.stop(); + root.transitionProgress = 0.0; + root.effectActive = false; + root._renderSettling = true; + renderSettleTimer.restart(); + root.screenScale = CompositorService.getScreenScale(modelData); + currentWallpaper.source = newSource; + nextWallpaper.source = ""; + } + + function startTransition() { + currentWallpaper.layer.enabled = true; + nextWallpaper.layer.enabled = true; + root.useNextForEffect = true; + root.effectActive = true; + if (srcCurrent.scheduleUpdate) + srcCurrent.scheduleUpdate(); + if (srcNext.scheduleUpdate) + srcNext.scheduleUpdate(); + transitionDelayTimer.start(); + } + + Timer { + id: transitionDelayTimer + interval: 16 + repeat: false + onTriggered: transitionAnimation.start() + } + + function changeWallpaper(newPath, force) { + if (!force && newPath === currentWallpaper.source) + return; + if (!newPath || newPath.startsWith("#")) + return; + root.screenScale = CompositorService.getScreenScale(modelData); + if (root.transitioning || root.effectActive) { + root.pendingWallpaper = newPath; + return; + } + if (!currentWallpaper.source) { + setWallpaperImmediate(newPath); + return; + } + + if (root.transitionType === "random") { + root.actualTransitionType = SessionData.includedTransitions.length === 0 ? "none" : SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)]; + } + + if (root.actualTransitionType === "none") { + setWallpaperImmediate(newPath); + return; + } + + switch (root.actualTransitionType) { + case "wipe": + root.wipeDirection = Math.random() * 4; + break; + case "disc": + case "pixelate": + case "portal": + root.discCenterX = Math.random(); + root.discCenterY = Math.random(); + break; + case "stripes": + root.stripesCount = Math.round(Math.random() * 20 + 4); + root.stripesAngle = Math.random() * 360; + break; + } + + root._renderSettling = true; + renderSettleTimer.restart(); + + nextWallpaper.source = newPath; + + if (nextWallpaper.status === Image.Ready) + root.startTransition(); + } + + Loader { + anchors.fill: parent + active: !root.source || root.isColorSource + asynchronous: true + + sourceComponent: DankBackdrop { + screenName: modelData.name + } + } + + readonly property int maxTextureSize: 8192 + property real screenScale: 1 + property int textureWidth: Math.min(Math.round(modelData.width * screenScale), maxTextureSize) + property int textureHeight: Math.min(Math.round(modelData.height * screenScale), maxTextureSize) + + Image { + id: currentWallpaper + anchors.fill: parent + visible: true + opacity: 1 + layer.enabled: false + asynchronous: true + retainWhileLoading: true + smooth: true + cache: true + sourceSize: Qt.size(root.textureWidth, root.textureHeight) + fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name)) + } + + Image { + id: nextWallpaper + anchors.fill: parent + visible: source !== "" + opacity: 0 + layer.enabled: false + asynchronous: true + retainWhileLoading: true + smooth: true + cache: true + sourceSize: Qt.size(root.textureWidth, root.textureHeight) + fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name)) + + onStatusChanged: { + if (status !== Image.Ready) + return; + if (root.actualTransitionType === "none") { + root._renderSettling = true; + renderSettleTimer.restart(); + currentWallpaper.source = source; + nextWallpaper.source = ""; + root.transitionProgress = 0.0; + } else if (!root.transitioning) { + root.startTransition(); + } + } + } + + ShaderEffectSource { + id: srcCurrent + sourceItem: root.effectActive ? currentWallpaper : null + hideSource: root.effectActive + live: root.effectActive + mipmap: false + recursive: false + textureSize: Qt.size(root.textureWidth, root.textureHeight) + } + + ShaderEffectSource { + id: srcNext + sourceItem: root.effectActive ? nextWallpaper : null + hideSource: root.effectActive + live: root.effectActive + mipmap: false + recursive: false + textureSize: Qt.size(root.textureWidth, root.textureHeight) + } + + Rectangle { + id: dummyRect + width: 1 + height: 1 + visible: false + color: "transparent" + } + + ShaderEffectSource { + id: srcDummy + sourceItem: dummyRect + hideSource: true + live: false + mipmap: false + recursive: false + } + + Loader { + id: effectLoader + anchors.fill: parent + active: root.effectActive + + function getTransitionComponent(type) { + switch (type) { + case "fade": + return fadeComp; + case "wipe": + return wipeComp; + case "disc": + return discComp; + case "stripes": + return stripesComp; + case "iris bloom": + return irisComp; + case "pixelate": + return pixelateComp; + case "portal": + return portalComp; + default: + return null; + } + } + + sourceComponent: getTransitionComponent(root.actualTransitionType) + } + + Component { + id: fadeComp + ShaderEffect { + anchors.fill: parent + property variant source1: srcCurrent + property variant source2: root.useNextForEffect ? srcNext : srcDummy + property real progress: root.transitionProgress + property real fillMode: root.shaderFillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: modelData.width + property real imageHeight1: modelData.height + property real imageWidth2: modelData.width + property real imageHeight2: modelData.height + property real screenWidth: modelData.width + property real screenHeight: modelData.height + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_fade.frag.qsb") + } + } + + Component { + id: wipeComp + ShaderEffect { + anchors.fill: parent + property variant source1: srcCurrent + property variant source2: root.useNextForEffect ? srcNext : srcDummy + property real progress: root.transitionProgress + property real smoothness: root.edgeSmoothness + property real direction: root.wipeDirection + property real fillMode: root.shaderFillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: modelData.width + property real imageHeight1: modelData.height + property real imageWidth2: modelData.width + property real imageHeight2: modelData.height + property real screenWidth: modelData.width + property real screenHeight: modelData.height + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_wipe.frag.qsb") + } + } + + Component { + id: discComp + ShaderEffect { + anchors.fill: parent + property variant source1: srcCurrent + property variant source2: root.useNextForEffect ? srcNext : srcDummy + 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 + property real fillMode: root.shaderFillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: modelData.width + property real imageHeight1: modelData.height + property real imageWidth2: modelData.width + property real imageHeight2: modelData.height + property real screenWidth: modelData.width + property real screenHeight: modelData.height + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_disc.frag.qsb") + } + } + + Component { + id: stripesComp + ShaderEffect { + anchors.fill: parent + property variant source1: srcCurrent + property variant source2: root.useNextForEffect ? srcNext : srcDummy + 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 + property real fillMode: root.shaderFillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: modelData.width + property real imageHeight1: modelData.height + property real imageWidth2: modelData.width + property real imageHeight2: modelData.height + property real screenWidth: modelData.width + property real screenHeight: modelData.height + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_stripes.frag.qsb") + } + } + + Component { + id: irisComp + ShaderEffect { + anchors.fill: parent + property variant source1: srcCurrent + property variant source2: root.useNextForEffect ? srcNext : srcDummy + property real progress: root.transitionProgress + property real smoothness: root.edgeSmoothness + property real centerX: 0.5 + property real centerY: 0.5 + property real aspectRatio: root.width / root.height + property real fillMode: root.shaderFillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: modelData.width + property real imageHeight1: modelData.height + property real imageWidth2: modelData.width + property real imageHeight2: modelData.height + property real screenWidth: modelData.width + property real screenHeight: modelData.height + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_iris_bloom.frag.qsb") + } + } + + Component { + id: pixelateComp + ShaderEffect { + anchors.fill: parent + property variant source1: srcCurrent + property variant source2: root.useNextForEffect ? srcNext : srcDummy + property real progress: root.transitionProgress + property real smoothness: root.edgeSmoothness + property real fillMode: root.shaderFillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: modelData.width + property real imageHeight1: modelData.height + property real imageWidth2: modelData.width + property real imageHeight2: modelData.height + property real screenWidth: modelData.width + property real screenHeight: modelData.height + property real centerX: root.discCenterX + property real centerY: root.discCenterY + property real aspectRatio: root.width / root.height + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_pixelate.frag.qsb") + } + } + + Component { + id: portalComp + ShaderEffect { + anchors.fill: parent + property variant source1: srcCurrent + property variant source2: root.useNextForEffect ? srcNext : srcDummy + 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 + property real fillMode: root.shaderFillMode + property vector4d fillColor: root.fillColor + property real imageWidth1: modelData.width + property real imageHeight1: modelData.height + property real imageWidth2: modelData.width + property real imageHeight2: modelData.height + property real screenWidth: modelData.width + property real screenHeight: modelData.height + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wp_portal.frag.qsb") + } + } + + NumberAnimation { + id: transitionAnimation + target: root + property: "transitionProgress" + from: 0.0 + to: 1.0 + duration: root.actualTransitionType === "none" ? 0 : 1000 + easing.type: Easing.InOutCubic + onFinished: { + if (nextWallpaper.source && nextWallpaper.status === Image.Ready) { + currentWallpaper.source = nextWallpaper.source; + } + root.useNextForEffect = false; + nextWallpaper.source = ""; + root.transitionProgress = 0.0; + currentWallpaper.layer.enabled = false; + nextWallpaper.layer.enabled = false; + root._renderSettling = true; + renderSettleTimer.restart(); + root.effectActive = false; + + if (!root.pendingWallpaper) + return; + var pending = root.pendingWallpaper; + root.pendingWallpaper = ""; + Qt.callLater(() => root.changeWallpaper(pending, true)); + } + } + + Loader { + id: overviewBlurLoader + anchors.fill: parent + active: root.overviewBlurActive + + sourceComponent: MultiEffect { + anchors.fill: parent + source: effectLoader.active ? effectLoader.item : currentWallpaper + blurEnabled: true + blur: 0.8 + blurMax: 75 + autoPaddingEnabled: false + } + } + } + } +} |