From 63e90a5c175c43281a605b59e7ce6da0e528899e Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 29 Aug 2025 16:26:48 -0400 Subject: [PATCH] Wallpaper: cool fade in transition via shader --- Modules/Background/Background.qml | 84 +++++++++++++++++++++++++++++- Services/WallpaperService.qml | 5 +- Shaders/frag/mix_images.frag | 22 ++++++++ Shaders/qsb/mix_images.frag.qsb | Bin 0 -> 1548 bytes 4 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 Shaders/frag/mix_images.frag create mode 100644 Shaders/qsb/mix_images.frag.qsb diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index ef6cd39..b896099 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -5,6 +5,7 @@ import qs.Commons import qs.Services Variants { + id: backgroundVariants model: Quickshell.screens delegate: Loader { @@ -14,6 +15,25 @@ Variants { active: Settings.isLoaded && WallpaperService.getWallpaper(modelData.name) sourceComponent: PanelWindow { + id: root + + readonly property real transitionDuration: Settings.data.wallpaper.transitionDuration + readonly property real transitionType: Settings.data.wallpaper.transitionType + + property string servicedWallpaper: WallpaperService.getWallpaper(modelData.name) + onServicedWallpaperChanged: { + if (servicedWallpaper && servicedWallpaper !== currentWallpaper.source) { + if (Settings.data.wallpaper.transitionType == 'fade') { + setWallpaperWithTransition(servicedWallpaper) + } else { + setWallpaperImmediate(servicedWallpaper) + } + } + } + + // Internal state management + property bool transitioning: false + property real fadeValue: 0.0 color: Color.transparent screen: modelData @@ -29,12 +49,74 @@ Variants { } Image { + id: currentWallpaper anchors.fill: parent fillMode: Image.PreserveAspectCrop - source: WallpaperService.getWallpaper(modelData.name) + source: "" cache: true smooth: true mipmap: false + visible: false + } + + Image { + id: nextWallpaper + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + source: "" + cache: true + smooth: true + mipmap: false + visible: false + } + + ShaderEffect { + id: shaderEffect + anchors.fill: parent + + property variant source1: currentWallpaper + property variant source2: nextWallpaper + property real fade: fadeValue + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/mix_images.frag.qsb") + } + + // Animation for the fade value + NumberAnimation { + id: fadeAnimation + target: root + property: "fadeValue" + from: 0.0 + to: 1.0 + duration: Settings.data.wallpaper.transitionDuration + easing.type: Easing.InOutQuad + + onFinished: { + // Swap images after transition completes + currentWallpaper.source = nextWallpaper.source + fadeValue = 0.0 + transitioning = false + } + } + + function startTransition() { + if (!transitioning && nextWallpaper.source != currentWallpaper.source) { + transitioning = true + fadeAnimation.start() + } + } + + function setWallpaperImmediate(source) { + currentWallpaper.source = source + nextWallpaper.source = source + fadeValue = 0.0 + transitioning = false + } + + function setWallpaperWithTransition(source) { + if (source != currentWallpaper.source) { + nextWallpaper.source = source + startTransition() + } } } } diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index daafb97..ea3a594 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -163,9 +163,8 @@ Singleton { changeWallpaper(screenName, randomPath) } } - } - else { - // Pick a random wallpaper for all screens + } else { + // Pick a random wallpaper common to all screens // We can use any screenName here, so we just pick the primary one. var wallpaperList = getWallpapersList(Screen.name) if (wallpaperList.length > 0) { diff --git a/Shaders/frag/mix_images.frag b/Shaders/frag/mix_images.frag new file mode 100644 index 0000000..10c3a63 --- /dev/null +++ b/Shaders/frag/mix_images.frag @@ -0,0 +1,22 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(binding = 1) uniform sampler2D source1; +layout(binding = 2) uniform sampler2D source2; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float fade; +}; + +void main() { + vec4 color1 = texture(source1, qt_TexCoord0); + vec4 color2 = texture(source2, qt_TexCoord0); + + // Smooth cross-fade using smoothstep for better visual quality + float smoothFade = smoothstep(0.0, 1.0, fade); + + // Mix the two textures based on fade value + fragColor = mix(color1, color2, smoothFade) * qt_Opacity; +} \ No newline at end of file diff --git a/Shaders/qsb/mix_images.frag.qsb b/Shaders/qsb/mix_images.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..e06417c6f07afa3020f1dea72d0ef13311f0fd73 GIT binary patch literal 1548 zcmV+n2J`s<02HBkob6cKZrer>9ZPcTFlp}hq{$|3Q#w%@iE)a+iroZu(*#K1AU4th zfzK$9~(b9VO3&XN`Y z90q_2089YD1$YetJXiw`eOQ775=fy1H2^390Q^A*j5(0t6oin14{aITk>QH|z|cdm z0Xc;7&V#$)0>BdkQLjQh7Cr<1i(4z_aT|v5E(HdN&GGCkM!6={9{ z`30!JhlI!ZT_sJ?hd8A}1+Gy(S82>^q!0SeLPhG$D5u+G-!SGdL-(5R6WSA|h!1U> zd^+ACzRzG%#_AqM%x_SV>!(5WIBamCea5g+k4t1PKt<|)j5J3Zbv;Fzeg%^fcbYKh zUxc#cKW)UHBh7XE`9k~!!knl0Gvs>&lTwS1NUs~jfx3K5{-ZE0?VB|JI@RwAVUX_< zJ;zuaZgW} zzBc)OhY7f^I-^2+&;;#s)Giah&TR2Ci;|etYc;Ev@*tAdX4Ky7v9Oc)qOr>z#iC(Z znMm_Mu#G&l-dKz=*7e04do8@@zDT2fL;55GI#+sr5Q%Nm8~QEYuyz`jmB&$-q+R4< z-d)%5r~xy-+w1VuU1nL5rva}w4ges2~rH%Y|iW531O0vVa$J-g=A zs;rKGyt}T00#R7I<2_FO_OdVhW^J71!-r4sK`-2cn^{$>5qDC^I|vflfTmNkmUHENOz)n#Gox0i64zkTz*d+Yua zjXOyEZ|hlnJL@k}be{`0@-XD7tie1>c{|DkPwkDGmM2ZV^Sb^wNvb9>*Pp{7yg|X) zJnQ+vw@DagT-d?rj0s7}6rgjvpg8p+$l0D%t7T1Zkq=ZNi+XLCc?%qWRV|TK;S0_@ zLV2`sOmv3my?EUb-ufQA?pR(#Tbb}h6sYMH*QWVwbb(TTt-g5r@|CxgCRye^jeEJE za*SbP^K9*lua{O#> zE{eT(GZ+m-LKZj%!oFWag|ef<>R?@sX{ioRd%JpS$m#U^{XVsnS7@o-(yC2mSJ_x5 z{5JO@yw@U*6mP*qIHRjXCrmX^jxj?khC~_)WO^KP&c;%c-OWXw#sz{MbH~7MdHsJ7 ze$wBciQ=G>w>W#y<-+fHnP_#QjYVtd&!TSY4*qdA7cDE#@Ixy0yFBap0cSF5+$Z2* ze|fK$3DIHJHJr&_OTF2Ym9}fM>+548qj;Z2Yc}(Yr*$uk9O!%>!{7qb5tl3oiuuQ8u_#AAhJ!!brHJkc%voFv!fOQGfyA%2q|hwm-nlBf ygWkGEZzZawu%{-aYV;Z|yo%}PANmEa@FN9c(Z;jTkZ&EQf9NR2%i~XD`kmn)s~n5~ literal 0 HcmV?d00001