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 000000000..2fe973460 Binary files /dev/null and b/Shaders/qsb/weather_cloud.frag.qsb differ