From 3c7d03ada907584ed0103983861c6b73be7d2742 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Sat, 30 Aug 2025 11:22:09 -0400 Subject: [PATCH] Wallpaper: added a bash script to compile all shaders + code cleanup --- Bin/shaders-compile.sh | 36 ++++++++++++++++ Modules/Background/Background.qml | 44 ++++++++------------ Modules/SettingsPanel/Tabs/WallpaperTab.qml | 2 - Services/WallpaperService.qml | 19 ++------- Shaders/frag/wp_stripes.frag | 26 ++++++++---- Shaders/qsb/wp_stripes.frag.qsb | Bin 5031 -> 5147 bytes 6 files changed, 75 insertions(+), 52 deletions(-) create mode 100755 Bin/shaders-compile.sh diff --git a/Bin/shaders-compile.sh b/Bin/shaders-compile.sh new file mode 100755 index 0000000..b9f76a8 --- /dev/null +++ b/Bin/shaders-compile.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Directory containing the source shaders. +SOURCE_DIR="Shaders/frag/" + +# Directory where the compiled shaders will be saved. +DEST_DIR="Shaders/qsb/" + +# Check if the source directory exists. +if [ ! -d "$SOURCE_DIR" ]; then + echo "Source directory $SOURCE_DIR not found!" + exit 1 +fi + +# Create the destination directory if it doesn't exist. +mkdir -p "$DEST_DIR" + +# Loop through all files in the source directory ending with .frag +for shader in "$SOURCE_DIR"*.frag; do + # Check if a file was found (to handle the case of no .frag files). + if [ -f "$shader" ]; then + # Get the base name of the file (e.g., wp_fade). + shader_name=$(basename "$shader" .frag) + + # Construct the output path for the compiled shader. + output_path="$DEST_DIR$shader_name.frag.qsb" + + # Construct and run the qsb command. + qsb --qt6 -o "$output_path" "$shader" + + # Print a message to confirm compilation. + echo "Compiled $shader to $output_path" + fi +done + +echo "Shader compilation complete." \ No newline at end of file diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index df13fc6..4915386 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -12,14 +12,14 @@ Variants { required property ShellScreen modelData - active: Settings.isLoaded && WallpaperService.getWallpaper(modelData.name) + active: Settings.isLoaded sourceComponent: PanelWindow { id: root // Internal state management property bool firstWallpaper: true - property string transitionType: 'fade' + property string transitionType: "fade" property bool transitioning: false property real transitionProgress: 0 property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness @@ -40,7 +40,6 @@ Variants { property string servicedWallpaper: WallpaperService.getWallpaper(modelData.name) onServicedWallpaperChanged: { if (servicedWallpaper && servicedWallpaper !== currentWallpaper.source) { - // Set wallpaper immediately on startup if (firstWallpaper) { firstWallpaper = false @@ -51,36 +50,25 @@ Variants { // Get the transitionType from the settings transitionType = Settings.data.wallpaper.transitionType - if (transitionType == 'random') { + 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' + transitionType = "fade" } - Logger.log("Background", "New wallpaper: ", servicedWallpaper, "On:", modelData.name, "Transition:", transitionType) + Logger.log("Background", "New wallpaper: ", servicedWallpaper, "On:", modelData.name, "Transition:", + transitionType) switch (transitionType) { case "none": setWallpaperImmediate(servicedWallpaper) break - case "wipe_left": - wipeDirection = 0 - setWallpaperWithTransition(servicedWallpaper) - break - case "wipe_right": - wipeDirection = 1 - setWallpaperWithTransition(servicedWallpaper) - break - case "wipe_up": - wipeDirection = 2 - setWallpaperWithTransition(servicedWallpaper) - break - case "wipe_down": - wipeDirection = 3 + case "wipe": + wipeDirection = Math.random() * 4 setWallpaperWithTransition(servicedWallpaper) break case "disc": @@ -89,7 +77,7 @@ Variants { setWallpaperWithTransition(servicedWallpaper) break case "stripes": - stripesCount = Math.round(Math.random() * 24 + 2) + stripesCount = Math.round(Math.random() * 24 + 6) stripesAngle = Math.random() * 360 setWallpaperWithTransition(servicedWallpaper) break @@ -139,7 +127,7 @@ Variants { ShaderEffect { id: fadeShader anchors.fill: parent - visible: transitionType === 'fade' || transitionType === 'none' + visible: transitionType === "fade" || transitionType === "none" property variant source1: currentWallpaper property variant source2: nextWallpaper @@ -151,7 +139,7 @@ Variants { ShaderEffect { id: wipeShader anchors.fill: parent - visible: transitionType.startsWith('wipe_') + visible: transitionType === "wipe" property variant source1: currentWallpaper property variant source2: nextWallpaper @@ -166,7 +154,7 @@ Variants { ShaderEffect { id: discShader anchors.fill: parent - visible: transitionType === 'disc' + visible: transitionType === "disc" property variant source1: currentWallpaper property variant source2: nextWallpaper @@ -183,7 +171,7 @@ Variants { ShaderEffect { id: stripesShader anchors.fill: parent - visible: transitionType === 'stripes' + visible: transitionType === "stripes" property variant source1: currentWallpaper property variant source2: nextWallpaper @@ -203,8 +191,10 @@ Variants { property: "transitionProgress" from: 0.0 to: 1.0 - duration: Settings.data.wallpaper.transitionDuration ?? 1000 - easing.type: Easing.InOutCubic //transitionType.startsWith('wipe_') ? Easing.InOutCubic : Easing.OutQuad + // The stripes shader feels faster visually, we make it a bit slower here. + duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration + * 1.4 : Settings.data.wallpaper.transitionDuration + easing.type: Easing.InOutCubic onFinished: { // Swap images after transition completes currentWallpaper.source = nextWallpaper.source diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index 71dd82e..8212b4a 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -249,8 +249,6 @@ ColumnLayout { } } } - - } // Reusable component for interval preset chips diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 403153a..5e7bed6 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -36,24 +36,12 @@ Singleton { name: "Stripes" } ListElement { - key: "wipe_left" - name: "Wipe Left" - } - ListElement { - key: "wipe_right" - name: "Wipe Right" - } - ListElement { - key: "wipe_up" - name: "Wipe Up" - } - ListElement { - key: "wipe_down" - name: "Wipe Down" + key: "wipe" + name: "Wipe" } } - // All transition keys but filter out "random" + // All transition keys but filter out "none" and "random" so we are left with the real transitions readonly property var allTransitions: Array.from({ "length": transitionsModel.count }, (_, i) => transitionsModel.get(i).key).filter( @@ -154,7 +142,6 @@ Singleton { } //Logger.log("Wallpaper", "setWallpaper on", screenName, ": ", path) - var wallpaperChanged = false var monitor = getMonitorConfig(screenName) diff --git a/Shaders/frag/wp_stripes.frag b/Shaders/frag/wp_stripes.frag index 7d12b30..61a20f7 100644 --- a/Shaders/frag/wp_stripes.frag +++ b/Shaders/frag/wp_stripes.frag @@ -65,16 +65,28 @@ void main() { // Use absolute stripe position for consistent delay across all stripes float normalizedStripePos = clamp(stripePos / stripes, 0.0, 1.0); - // Reduced delay factor and better scaling to match other shaders' timing - float maxDelay = 0.15; // Maximum delay for the last stripe + // Increased delay and better distribution + float maxDelay = 0.15; float stripeDelay = normalizedStripePos * maxDelay; - // Ensure all stripes complete when progress reaches 1.0 - // without making the overall animation appear faster - float stripeProgress = clamp((ubuf.progress - stripeDelay) / (1.0 - maxDelay), 0.0, 1.0); + // Better progress mapping that uses the full 0.0-1.0 range + // Map progress so that: + // - First stripe starts at progress = 0.0 + // - Last stripe finishes at progress = 1.0 + float stripeProgress; + if (ubuf.progress <= stripeDelay) { + stripeProgress = 0.0; + } else if (ubuf.progress >= (stripeDelay + (1.0 - maxDelay))) { + stripeProgress = 1.0; + } else { + // Scale the progress within the active window for this stripe + float activeStart = stripeDelay; + float activeEnd = stripeDelay + (1.0 - maxDelay); + stripeProgress = (ubuf.progress - activeStart) / (activeEnd - activeStart); + } - // Apply smooth easing - stripeProgress = smoothstep(0.0, 1.0, stripeProgress); + // Use gentler easing curve + stripeProgress = stripeProgress * stripeProgress * (3.0 - 2.0 * stripeProgress); // Smootherstep instead of smoothstep // Use the perpendicular coordinate for edge comparison float yPos = perpCoord; diff --git a/Shaders/qsb/wp_stripes.frag.qsb b/Shaders/qsb/wp_stripes.frag.qsb index 14cfb3e9be1ea1c8f60a73d309ab9835601d9570..0d8d94e99c00cee0789221ded7cbba6a616f09fb 100644 GIT binary patch literal 5147 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 z zga@q9@g`y?CStLg>y$iyhMY8OhifH;CAtz=Gu4Xir+1rySMv*YYGMIEdLkA(YWZ`H zTQ-l{g*5eT%AVcjd44fTZJtv!DcJFTgoUGON6nlDMO(%!cool2nK?7GXTzFrr+3HU z%%E92+OH$iEs$viDMB%0RjYRKU_DG`&YW>(ykMOQvcebli+%L)FkT?y=#LaB5jsRGqmMKP#*qs*%VR?*CvzEyNAHxMpz zexw_Av22TIQY5wDs=$I5>@;)qD=xK16a|h;?SbRAX-AJepS+6KjwWW!0j{;*+{EcQ zbAW4!h*fRBS{I5BkyyXPDRNv-WS%(gr0rMv^l;lW=(M#;Q2SZ3qdM3KeUn+OS}5 zmTu9#o*Xn&iDZlJ?6xb`oSAE>b71~OB65!vZ>t5NU7W;n&u2B=WODsC2h5aImL49< zq{gzN_QB&w)!;t8Byvc z8|T;PNGdy)%A}KH<71g&u^L7q)MP`fdqHSd;}SZkp(4SZ6Jt`pVfDNCfaR8Hu;sKL zlJ{uzp76_#OQBR2rU5gZNb-r=(6himHLY=zUf_g|=Qh@CWeVyGwJJcqNS8OkKN>wb z?HYrvc(;jUcu$1ya_+ZX<(n*G`5LNQ+;lP)WpIPr7uA0PMrT08zaizRF&K4yQ>2Ne z#6mrXEn0$xPJ_jAC4a4Nm3LdAHJv|~vtth1AKcCt=fovgEyb#}7O)17tRuVkJ5 zjjS{KyvsTrx7BM-uNI2>M9H_xyQo}tYF>RV_QhxCwWplvo!6e7*Pfl%o}Jg8o!6e7 z*Pfl%p3nNVXMO+jT)**D4L_GJJDcow-g7?d_ngY6_Y}S2tZQuhcC&$K_hPewI6K~H z&L73az0TC**8C<@k6ZIgOg(PxGs(uivv3kg=YQ$D2=dROTwFL@E0t`Yr(|19-!3~r zX#4TQNtrD9-m(0Qb(`mlamvhQxQ%>0voyDuRjY8jS1JW|7%xPRY)L5G(Xg3jY<;*j zWMZxvtDe}@;m4cdJZo(N9&4euafmdE&bCn6$Z>TrcbU@ODS0e*s|evXnd7Pv4=Hd5 zMXGx+v_jj=1C>VttfB)#x6_-B=uNfJn{KI>(6J!2LZ=|Qspr^gTQt!|1>)f3-UBz~ zclllre%Dtf(sFwW>mMjwar+Hsyb25L#tB@6czNjYYD+oQg=aNX{C`oM5ytEi`zX zkdOEVC!sD6+GzN8So2*?!3ni!(t|(dh-=M58>+rlp0Q|e;+D;#W0hSmpsd&_yPj_s z%`1m)o^gV}amzzh-wVBPu4)HY#$p>Bw@|4SZFB349a@!q5Ed)W;cW^(aAt~|>7T@r zZLwI5RG6+cV+U2MV4GZOA{N`c+1yL%`CMQc}TG?N`pkEgR#AUM(DxIw)- zbY>knfS)vyNgCaDb%g0@{?v#k`wtyzA?djDR57NfYk}>@D_+5Eh?8m>o`z}hDJ~V7 zj%!X&%b9=Z5IvpUAhcW_)vNeDXHHLx`W*Lbu>J+>qNw;Z3Q5z`@(9Abz_bEPaiFXL zLD9jPYK4LUHnc@SX&pqGPohK%m$}f(aSbPOW7?CG1xambyUm{Y%;m&u_ zJBKn(vglH>f^f?gs+Kf9o%EN!=T&tIzMT&J)~s~;RD4r?URD2JzppMj{k;9wy4wBD zch@`LUGIE%ef}zpr{x{34swY7%V{~vp__j$k8`xN{Ufi)oE=|XkCtL0k$e{An1!k_ z5xs3IGEr2!s!SAh<{tBi7G)L_*~JxRTE()tIMXVY%>^2-MJ(GFX`=U8tK%PpLN4!# x!Uk0~dU=|L^B~%p5d5!7_z%Cd|7(Y^M9aU&r<6q&Ehqn8o-WU?9{}KJR>8^@N67#H