From 48f38ea4b2d468502214eda667c60400e50771b1 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Fri, 6 Feb 2026 16:16:32 +0100 Subject: [PATCH] Added weather widget effects for Cloudy and Foggy weather --- Modules/Cards/WeatherCard.qml | 23 ++++-- Shaders/frag/weather_cloud.frag | 123 +++++++++++++++++++++++++++++ Shaders/qsb/weather_cloud.frag.qsb | Bin 0 -> 6082 bytes 3 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 Shaders/frag/weather_cloud.frag create mode 100644 Shaders/qsb/weather_cloud.frag.qsb diff --git a/Modules/Cards/WeatherCard.qml b/Modules/Cards/WeatherCard.qml index 27d2fa263..b7f56a245 100644 --- a/Modules/Cards/WeatherCard.qml +++ b/Modules/Cards/WeatherCard.qml @@ -15,13 +15,15 @@ NBox { property bool showEffects: Settings.data.location.weatherShowEffects readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) - // Test mode: set to "rain" or "snow" + // Test mode: set to "rain", "snow", "cloud" or "fog" property string testEffects: "" // Weather condition detection readonly property int currentWeatherCode: weatherReady ? LocationService.data.weather.current_weather.weathercode : 0 - readonly property bool isRaining: testEffects === "rain" || (testEffects === "" && ((currentWeatherCode >= 51 && currentWeatherCode <= 67) || (currentWeatherCode >= 80 && currentWeatherCode <= 82))) - readonly property bool isSnowing: testEffects === "snow" || (testEffects === "" && ((currentWeatherCode >= 71 && currentWeatherCode <= 77) || (currentWeatherCode >= 85 && currentWeatherCode <= 86))) + readonly property bool isRaining: testEffects === "rain" || (testEffects === "" && ((currentWeatherCode >= 51 && currentWeatherCode <= 67) || (currentWeatherCode >= 80 && currentWeatherCode <= 82))) + readonly property bool isSnowing: testEffects === "snow" || (testEffects === "" && ((currentWeatherCode >= 71 && currentWeatherCode <= 77) || (currentWeatherCode >= 85 && currentWeatherCode <= 86))) + readonly property bool isCloudy: testEffects === "cloud" || (testEffects === "" && (currentWeatherCode === 3)) + readonly property bool isFoggy: testEffects === "fog" || (testEffects === "" && (currentWeatherCode === 45 || currentWeatherCode === 48)) visible: Settings.data.location.weatherEnabled implicitHeight: Math.max(100 * Style.uiScaleRatio, content.implicitHeight + (Style.marginXL * 2)) @@ -30,7 +32,7 @@ NBox { Loader { id: weatherEffectLoader anchors.fill: parent - active: root.showEffects && (root.isRaining || root.isSnowing) + active: root.showEffects && (root.isRaining || root.isSnowing || root.isCloudy || root.isFoggy) sourceComponent: Item { anchors.fill: parent @@ -47,8 +49,8 @@ NBox { ShaderEffect { id: weatherEffect anchors.fill: parent - // Snow fills the box, rain matches content margins - anchors.margins: root.isSnowing ? root.border.width : Style.marginXL + // rain matches content margins, everything else fills the box + anchors.margins: root.isRaining ? Style.marginXL : root.border.width property var source: ShaderEffectSource { sourceItem: content @@ -59,9 +61,14 @@ NBox { property real itemWidth: weatherEffect.width property real itemHeight: weatherEffect.height property color bgColor: root.color - property real cornerRadius: root.isSnowing ? (root.radius - root.border.width) : 0 + property real cornerRadius: root.isRaining ? 0 : (root.radius - root.border.width) + property real alternative: root.isFoggy; - fragmentShader: root.isSnowing ? Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/weather_snow.frag.qsb") : Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/weather_rain.frag.qsb") + fragmentShader: + root.isSnowing ? Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/weather_snow.frag.qsb") : + root.isRaining ? Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/weather_rain.frag.qsb") : + root.isCloudy || root.isFoggy ? Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/weather_cloud.frag.qsb") : + "" } } } diff --git a/Shaders/frag/weather_cloud.frag b/Shaders/frag/weather_cloud.frag new file mode 100644 index 000000000..1991ce339 --- /dev/null +++ b/Shaders/frag/weather_cloud.frag @@ -0,0 +1,123 @@ +#version 450 +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float time; + float itemWidth; + float itemHeight; + vec4 bgColor; + float cornerRadius; + float alternative; +} ubuf; + +// Signed distance function for rounded rectangle +float roundedBoxSDF(vec2 center, vec2 size, float radius) { + vec2 q = abs(center) - size + radius; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius; +} + +float hash(vec2 p) { + p = fract(p * vec2(234.34, 435.345)); + p += dot(p, p + 34.23); + return fract(p.x * p.y); +} + +// Perlin-like noise +float noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); // Smooth interpolation + float a = hash(i); + float b = hash(i + vec2(1.0, 0.0)); + float c = hash(i + vec2(0.0, 1.0)); + float d = hash(i + vec2(1.0, 1.0)); + return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); +} + +// Turbulent noise for natural fog +float turbulence(vec2 p, float iTime) { + float t = 0.0; + float scale = 1.0; + for(int i = 0; i < 5; i++) { + t += abs(noise(p * scale + iTime * 0.1 * scale)) / scale; + scale *= 2.0; + } + return t; +} + +void main() { + vec2 uv = qt_TexCoord0; + + vec4 col = vec4(ubuf.bgColor.rgb, 1.0); + + // Different parameters for fog vs clouds + float timeSpeed, layerScale1, layerScale2, layerScale3; + float flowSpeed1, flowSpeed2; + float densityMin, densityMax; + float baseOpacity; + float pulseAmount; + + if (ubuf.alternative > 0.5) { + // Fog: slower, larger scale, more uniform + timeSpeed = 0.03; + layerScale1 = 1.0; + layerScale2 = 2.5; + layerScale3 = 2.0; + flowSpeed1 = 0.00; + flowSpeed2 = 0.02; + densityMin = 0.1; + densityMax = 0.9; + baseOpacity = 0.75; + pulseAmount = 0.05; + } else { + // Clouds: faster, smaller scale, puffier + timeSpeed = 0.08; + layerScale1 = 2.0; + layerScale2 = 4.0; + layerScale3 = 6.0; + flowSpeed1 = 0.03; + flowSpeed2 = 0.04; + densityMin = 0.35; + densityMax = 0.75; + baseOpacity = 0.4; + pulseAmount = 0.15; + } + + float iTime = ubuf.time * timeSpeed; + + // Create flowing patterns with multiple layers + vec2 flow1 = vec2(iTime * flowSpeed1, iTime * flowSpeed1 * 0.7); + vec2 flow2 = vec2(-iTime * flowSpeed2, iTime * flowSpeed2 * 0.8); + + float fog1 = noise(uv * layerScale1 + flow1); + float fog2 = noise(uv * layerScale2 + flow2); + float fog3 = turbulence(uv * layerScale3, iTime); + + float fogPattern = fog1 * 0.5 + fog2 * 0.3 + fog3 * 0.2; + float fogDensity = smoothstep(densityMin, densityMax, fogPattern); + + // Gentle pulsing + float pulse = sin(iTime * 0.4) * pulseAmount + (1.0 - pulseAmount); + fogDensity *= pulse; + + vec3 hazeColor = vec3(0.88, 0.90, 0.93); + float hazeOpacity = fogDensity * baseOpacity; + vec3 fogContribution = hazeColor * hazeOpacity; + float fogAlpha = hazeOpacity; + + vec3 resultRGB = fogContribution + col.rgb * (1.0 - fogAlpha); + float resultAlpha = fogAlpha + col.a * (1.0 - fogAlpha); + + // Calculate corner mask + vec2 pixelPos = qt_TexCoord0 * vec2(ubuf.itemWidth, ubuf.itemHeight); + vec2 center = pixelPos - vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + vec2 halfSize = vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5; + float dist = roundedBoxSDF(center, halfSize, ubuf.cornerRadius); + float cornerMask = 1.0 - smoothstep(-1.0, 0.0, dist); + + // Apply global opacity and corner mask + float finalAlpha = resultAlpha * ubuf.qt_Opacity * cornerMask; + fragColor = vec4(resultRGB * (finalAlpha / max(resultAlpha, 0.001)), finalAlpha); +} \ No newline at end of file diff --git a/Shaders/qsb/weather_cloud.frag.qsb b/Shaders/qsb/weather_cloud.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..2fe97346064877322147be42918abe65d1ae0157 GIT binary patch literal 6082 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 I1J0z)mkDV1z5oCK literal 0 HcmV?d00001